Mr.Raindrop

Mr.Raindrop

一切都在无可挽回的走向庸俗。
twitter
github

设计模式初探 --结构型模式

结构型模式是为解决怎样组装现有的类,设计它们的交互方式,从而达到实现一定的功能目的。结构型模式包容了对很多问题的解决。例如:扩展性(外观、组成、代理、装饰)、封装(适配器、桥接)。

在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。

适配器模式#

适配器模式(Adapter Pattern)是一种结构型设计模式,其核心作用是将一个类的接口转换为客户端期望的另一种接口,从而解决因接口不兼容而无法协同工作的类之间的协作问题。它类似于现实中的 “电源转换器”,通过中间层接口转换,让原本不匹配的组件能够协同工作。

主要组成部分#
  • 目标接口(Target):客户端期望的接口,定义了需要调用的方法。
  • 被适配者(Adaptee):需要适配的接口,已存在的类或接口,但其接口与目标不兼容。
  • 适配器(Adapter):连接目标与被适配者的中间层,通过继承或组合实现接口转换。
实现#
  • 类适配器(继承方式):适配器继承被适配者类,并实现目标接口。灵活性较低

    // 目标接口
    interface Target { void request(); }
    // 被适配者
    class Adaptee { void specificRequest() { /*...*/ } }
    // 适配器
    class Adapter extends Adaptee implements Target {
        @Override void request() { specificRequest(); }
    }
    
  • 对象适配器(组合方式):适配器持有被适配者的对象实例,通过委托调用方法。

    class Adapter implements Target {
        private Adaptee adaptee;
        public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }
        @Override void request() { adaptee.specificRequest(); }
    }
    
  • 接口适配器(缺省适配器):解决目标接口方法过多而客户端仅需部分实现的问题。它通过抽象类作为中间层,为接口方法提供默认实现(通常为空或简单逻辑),允许子类按需重写特定方法,从而避免强制实现所有接口方法的冗余。

    // 目标接口
    public interface MultiFunction {
        void print();
        void scan();
        void copy();
    }
    
    // 抽象适配器类
    public abstract class DefaultAdapter implements MultiFunction {
        @Override
        public void print() {}  // 默认空实现
        @Override
        public void scan() {}
        @Override
        public void copy() {}
    }
    
    // 具体适配器类
    public class PrinterAdapter extends DefaultAdapter {
        @Override
        public void print() {
            System.out.println("打印功能已启用");
        }
        // scan()和copy()无需重写,直接使用默认空逻辑
    }
    
    // 调用
    public class Client {
        public static void main(String[] args) {
            MultiFunction device = new PrinterAdapter();
            device.print();  // 输出:打印功能已启用
        }
    }
    

外观模式#

外观模式(Facade Pattern)是一种结构型设计模式,其核心目标是为复杂的子系统提供一个统一的、简化的接口,从而隐藏系统内部的复杂性,降低客户端与子系统之间的耦合度,提升系统的易用性和可维护性。

主要组成部分#

通过一个门面类(Facade) 封装多个子系统的交互逻辑,对外提供高层次的接口。客户端只需与门面类交互,无需直接调用复杂的子系统组件。

  • 门面角色(Facade):统一协调子系统的调用,对外提供简化接口。
  • 子系统角色(Subsystem Classes):实现具体功能的模块。
  • 客户端(Client)

image

桥接模式#

2. 桥接模式 — Graphic Design Patterns

设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要 4 个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:

  • 第一种设计方案是为每一种形状都提供一套各种颜色的版本。
  • 第二种设计方案是根据实际需要对形状和颜色进行组合

桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。将抽象部分与它的实现部分分离,使它们都可以独立地变化。

桥接模式的结构#

桥接设计模式

image

实现案例#

桥接模式(Bridge Pattern)-(最通俗易懂的案例)_桥接器模式实例 - CSDN 博客

组合模式#

将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些对象即可以是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户,它希望能够一致的对待容器对象和叶子对象。这就是组合模式的设计动机:组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无需区分,可以对它们进行一致的处理

组合模式(Composite Pattern) 也称为 整体 - 部分(Part-Whole)模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。

组合模式结构#

image
组合模式分为透明式的组合模式和安全式的组合模式。在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add ()、Remove () 及 GetChild () 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。

透明模式的实现#

27 设计模式 —— 组合模式(详解版) - 知乎

public class CompositePattern {
    public static void main(String[] args) {
        Component c0 = new Composite();
        Component c1 = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        Component leaf3 = new Leaf("3");
        c0.add(leaf1);
        c0.add(c1);
        c1.add(leaf2);
        c1.add(leaf3);
        c0.operation();
    }
}
//抽象构件
interface Component {
    public void add(Component c);
    public void remove(Component c);
    public Component getChild(int i);
    public void operation();
}
//树叶构件
class Leaf implements Component {
    private String name;
    public Leaf(String name) {
        this.name = name;
    }
    public void add(Component c) {
    }
    public void remove(Component c) {
    }
    public Component getChild(int i) {
        return null;
    }
    public void operation() {
        System.out.println("树叶" + name + ":被访问!");
    }
}
//树枝构件
class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<Component>();
    public void add(Component c) {
        children.add(c);
    }
    public void remove(Component c) {
        children.remove(c);
    }
    public Component getChild(int i) {
        return children.get(i);
    }
    public void operation() {
        for (Object obj : children) {
            ((Component) obj).operation();
        }
    }
}
安全模式实现#

由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
动图

Leaf 类不需要实现那些对于它来说无用的方法,如 add、remove 和 getChild,这使得设计更加合理且易于理解。同时,这样的设计也避免了潜在的错误

代理模式#

控制对一个对象的访问,通过引入一个代理对象来管理这个访问。代理模式允许在不改变原对象接口的情况下,增加额外的功能或控制访问方式。

主要组成部分#
  1. Subject(目标接口)
    • 定义了代理类和真实主题类的共同接口。
    • 这使得客户端可以在不改变代码的情况下,使用代理或者真实主题对象。
  2. Real Subject(真实主题)
    • 实现了 Subject 接口的具体业务逻辑。
    • 这是客户端真正需要访问的对象。
  3. Proxy(代理)
    • 也实现了 Subject 接口,并且维护一个对 Real Subject 的引用。
    • 代理类负责在客户端和真实主题之间进行协调,可能在请求前后执行一些额外的操作。

代理模式的结构图

静态代理#

一个代理只能服务于一个特定的业务实现类

1. 定义目标接口

首先,定义一个接口,该接口声明了需要被代理的方法。

JAVApublic interface Subject {
    void request();
}

2. 实现真实主题类

创建一个实现了上述接口的具体类,该类包含实际的业务逻辑。

JAVApublic class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: 处理请求");
    }
}

3. 创建代理类

代理类也实现相同的接口,并持有一个对真实主题对象的引用。代理类可以在调用真实主题的方法之前或之后添加额外的功能。

JAVApublic class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        // 执行预处理操作
        System.out.println("Proxy: 执行预处理操作");
        realSubject.request();
        // 执行后处理操作
        System.out.println("Proxy: 执行后处理操作");
    }
}

4. 编写客户端代码

客户端通过接口与代理类或真实主题对象交互,无需知道是直接调用还是通过代理。

JAVApublic class Client {
    public static void main(String[] args) {
        // 创建真实主题对象
        Subject realSubject = new RealSubject();
        
        // 创建代理对象,并将真实主题传递给代理
        Subject proxy = new Proxy(realSubject);
        
        // 调用代理的方法
        proxy.request();
    }
}
动态代理#

动态代理采用反射的机制,在运行时创建一个接口类的实例。

//业务接口
interface DateService {
    void add();
    void del();
}
// 具体业务类
class DateServiceImplA implements DateService {
    @Override
    public void add() {
        System.out.println("成功添加!");
    }

    @Override
    public void del() {
        System.out.println("成功删除!");
    }
}
// 代理
class ProxyInvocationHandler implements InvocationHandler {
    private DateService service;

    public ProxyInvocationHandler(DateService service) {
        this.service = service;
    }
    
    
	// this.getClass().getClassLoader() 第一个是类加载器,用来加载代理类。
    // service.getClass().getInterfaces()  被代理类实现的所有接口,使得代理对象能够提供与目标对象相同的接口视图。
    // this 当前实例(即实现了 InvocationHandler 接口的对象),用于处理对代理对象的方法调用。
    public Object getDateServiceProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
    }

    /* invoke 方法:
	重写的 invoke 方法会在代理对象上调用任何方法时触发。它接收三个参数:
	Object proxy:代表代理对象本身。
	Method method:表示正在被调用的目标方法。
	Object[] args:传递给目标方法的参数列表。
	在方法体内,首先通过 method.invoke(service, args); 调用了目标对象的实际方法,并将结果保存到 result 变量中。
	然后打印出一条日志信息,表明代理对象执行了某个方法,并记录了方法名和返回值。
	最后返回目标方法的执行结果。
	*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        var result = method.invoke(service, args); // 让service调用方法,方法返回值
        System.out.println(proxy.getClass().getName() + "代理类执行" + method.getName() + "方法,返回" + result +  ",记录日志!");
        return result;
    }
}

//客户端
public class Test {
    public static void main(String[] args) {
        DateService serviceA = new DateServiceImplA();
        DateService serviceProxy = (DateService) new ProxyInvocationHandler(serviceA).getDateServiceProxy();
        serviceProxy.add();
        serviceProxy.del();
    }
}

装饰模式#

装饰模式(Decorator Pattern)-(最通俗易懂的案例)_装饰模式使用例子 - CSDN 博客

装饰模式(Decorator), 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。装饰模式属于结构型模式,它是作为现有的类的一个包装。

Decorator 类图

装饰模式的主要角色包括:

  • Component(抽象组件):定义了被装饰对象和装饰器共同的接口。定义一个对象接口,可以给这些对象动态地添加职责。

  • ConcreteComponent(具体组件):实现了 Component 接口,是被装饰的对象。定义了一个具体的对象,也可以给这个对象添加一些职责。

  • Decorator(抽象装饰器):持有一个 Component 对象的引用,并定义了一个与 Component 具有相同接口的方法,以保持一致性。

  • ConcreteDecorator(具体装饰器):实现了 Decorator 定义的方法,在调用被装饰对象的方法前后可以执行一些额外的操作。

具体实现#
// 抽象组件
public interface INoodles {
    public void cook();
}

// 具体组件
public class Noodles implements INoodles {

    @Override
    public void cook() {
        System.out.println("的面条");
    }
}

// 抽象装饰类
public abstract class NoodlesDecorator implements INoodles {

    private INoodles noodles;    //添加一个INoodles 的引用

    public NoodlesDecorator(INoodles noodles){     //通过构造器来设置INoodles 
        this.noodles = noodles;
    }

    @Override
    public void cook() {
        if (noodles!=null){
            noodles.cook();
        }
    }

}

// 具体装饰类
public class EggDecorator extends NoodlesDecorator {

    public EggDecorator(INoodles noodles) {
        super(noodles);
    }

    /**
     * 重写父类的cook方法,并添加自己的实现,调用父类的cook方法,此cook方法是通过本类的构造器
     * EggDecorator(INoodles noodles)传入的noodles的cook操作
     */
    @Override
    public void cook() {
        System.out.println("加了一个荷包蛋");
        super.cook();
    }
}

// 具体装饰类
public class BeefDecorator extends NoodlesDecorator {

    public BeefDecorator(INoodles noodles) {
        super(noodles);
    }

    @Override
    public void cook() {
        System.out.println("加了一斤牛肉");
        super.cook();
    }
}

public class Client {

    public static void main(String[] args) {

        INoodles noodles1 = new Noodles();
        INoodles noodles1WithEgg = new EggDecorator(noodles1);
        noodles1WithEgg.cook();
    }
}
装饰模式和代理模式的区别#
  1. 意图不同:装饰模式关注的是增强对象的功能,而代理模式更注重于控制对对象的访问。
  2. 实现细节:在装饰模式中,装饰器和被装饰的对象必须实现相同的接口,这样才能保证装饰链中的对象能够相互替换。而在代理模式中,代理类和目标对象也常常实现相同的接口,但这主要是为了保持一致性,并不是必须的条件。
  3. 设计考量:使用装饰模式时,设计者更多考虑的是如何在不修改现有代码的情况下扩展功能;而使用代理模式时,则更多地考虑如何保护、管理对资源的访问。
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。