设计模式 第一次复学

创建者模式

单例模式(Singleton Pattern)

单例模式(Singleton)确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式实现分为两种饿汉式和懒汉式,即创建时机,一个是类加载时创建,一个是调用时创建。

饿汉式:

java 复制代码
public class Singleton{
    private static final Singleton singleton = new Singleton();
    private Singleton(){}  //私有化,外界不可以再产生该单例类
    public static Singleton getInstance(){ return singlton; }
}

线程安全版懒汉式:

java 复制代码
public class Singleton{
    private volatile static Singleton singleton = null;
    private Singleton(){}

    public static Singleton getSingleton(){ 
        if(singleton == null){  
            synchronized(Singleton.class) {
                if(singleton == null){
                    singleton = new Singleton();  
                }
            }   
        }       
        return singleton;
    }
}

线程安全版懒汉式(静态内部类方式):避免了类加载时直接初始化,用静态内部类封装。

java 复制代码
public class Singleton{
    private Singleton(){}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();    
    }     
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
}

序列化和反射方式会破坏单例模式(枚举除外)。

单例模式优点

  1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建,销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  2. 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生个单例对象,然后用永久驻留内存的方式来解决(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制)。
  3. 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

单例模式缺点

  1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢,因为接口对单例模式是没有任何意义的,它要求"自行实例化",并且提供单一实例,接口或抽象类是不可能被实例化的。当然在特殊情况下,单例模式可以实现接口,被继承等。
  2. 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
  3. 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把"要单例"和业务逻辑融合在一个类里。

单例模式使用场景

  1. 要求生成唯一的序列号的环境。
  2. 在整个项目中需要一个共享访问点或共享数据,例如一个web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的。
  3. 创建一个对象需要消耗的资源过多,如要昂问io和数据库等环境。
  4. 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然也可以直接声明喂static)。

工厂方法模式(Factory Method Pattern)

定义一个用于创建对象的接口,让子类决定该怎么实例化哪一个类。标准结构为一个抽象工厂类,具体工厂类,抽象产品类,产品类。其扩展有简单工厂方法模式 ,就是把抽象工厂类删除,违反了开闭原则;还有静态工厂类,就是把工厂方法设置成静态的。

java 复制代码
public interface CarFactory {  
    Car createCar();  
}
public class BMWFactory implements CarFactory {  
    @Override  
    public Car createCar() {  
        return new BMW();  
    }  
}  
  
public class AudiFactory implements CarFactory {  
    @Override  
    public Car createCar() {  
        return new Audi();  
    }  
}
public abstract class Car {  
    public abstract void show();  
}
public class BMW extends Car {  
    @Override  
    public void show() {  
        System.out.println("Creating BMW");  
    }  
}  
  
public class Audi extends Car {  
    @Override  
    public void show() {  
        System.out.println("Creating Audi");  
    }  
}
public class FactoryMethodDemo {  
    public static void main(String[] args) {  
        // 使用 BMW 工厂来创建 BMW  
        CarFactory bmwFactory = new BMWFactory();  
        Car bmw = bmwFactory.createCar();  
        bmw.show();  
  
        // 使用 Audi 工厂来创建 Audi  
        CarFactory audiFactory = new AudiFactory();  
        Car audi = audiFactory.createCar();  
        audi.show();  
    }  
}

工厂方法的优点

  1. 良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间耦合。
  2. 工厂方法的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成"拥抱变化。例如在之前的方法里只需要再写一个具体产品类即可完成功能扩展。
  3. 屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关系产品的接口,只要接口保持不变,系统中的上层模块就不会发生变化。因为产品类的实例化是由工厂类负责的,一个产品对象具体由哪个产品生成是由工具类决定的。
  4. 工厂方法模式是典型的解耦框架。高层模块只需要知道产品的抽象类,其他的实现类都不用去关系,符合迪米特法则;只依赖产品类的抽象,符合依赖倒置原则;当然也符合里氏替换原则,使用产品子类替换产品父类。

工厂方法的缺点

每需要一个新的不同对象,就要去写新的工厂类并去实现方法。

抽象工厂模式(Abstract Factory Pattern)

为创建一组相关或相互依赖的对象提供一个接口,而且无须指定他们的具体类。抽象工厂是工厂的升级版,工厂只能只能生产一种等级的产品,而抽象工厂可以生产多个等级的产品。

它由四部分构成

  • 抽象工厂:提供不同的工厂方法。
  • 具体工厂:对抽象的工厂方法予以实现。
  • 抽象产品:定义了不同种产品的属性等,该模式有多种抽象产品。
  • 具体产品:主要是对相应的抽象产品予以实现。
java 复制代码
// 抽象产品类(接口)
public interface Button {  
    void paint();  
}  
  
public interface TextField {  
    void paint();  
}
// 具体产品类
// Windows 平台的按钮  
public class WindowsButton implements Button {  
    @Override  
    public void paint() {  
        System.out.println("Windows Button painted.");  
    }  
}  
  
// Windows 平台的文本框  
public class WindowsTextField implements TextField {  
    @Override  
    public void paint() {  
        System.out.println("Windows TextField painted.");  
    }  
}  
  
// MacOS 平台的按钮  
public class MacButton implements Button {  
    @Override  
    public void paint() {  
        System.out.println("Mac Button painted.");  
    }  
}  
  
// MacOS 平台的文本框  
public class MacTextField implements TextField {  
    @Override  
    public void paint() {  
        System.out.println("Mac TextField painted.");  
    }  
}
// 抽象工厂类(接口)
public interface GUIFactory {  
    Button createButton();  
    TextField createTextField();  
}
// Windows GUI 工厂  
public class WindowsGUIFactory implements GUIFactory {  
    @Override  
    public Button createButton() {  
        return new WindowsButton();  
    }  
  
    @Override  
    public TextField createTextField() {  
        return new WindowsTextField();  
    }  
}  
  
// MacOS GUI 工厂  
public class MacGUIFactory implements GUIFactory {  
    @Override  
    public Button createButton() {  
        return new MacButton();  
    }  
  
    @Override  
    public TextField createTextField() {  
        return new MacTextField();  
    }  
}

public class FactoryPatternDemo {  
    public static void main(String[] args) {  
        // 使用 Windows GUI 工厂  
        GUIFactory factory = new WindowsGUIFactory();  
        Button button = factory.createButton();  
        TextField textField = factory.createTextField();  
  
        button.paint();  
        textField.paint();  
  
        // 假设需要切换到 MacOS GUI  
        factory = new MacGUIFactory();  
        button = factory.createButton();  
        textField = factory.createTextField();  
  
        button.paint();  
        textField.paint();  
    }  
}

和工厂方法模式的区别,工厂方法模式只有一种抽象产品,而抽象工厂模式(AFP),涉及到了多种抽象方法,会导致一个产品族的出现。工厂类工厂方法实现的方法势必要变多,一般情况,一个工厂方法对应一种抽象产品。这很好的扩展了工厂方法模式(FMP),对开闭原则进行了严格的遵守,但是也让FMP的缺点在AFP上更加明显,以前只能是增加一个具体实现工厂的需求,但是在AFP里,我如果要多增加一种抽象产品,比如在栗子中我的GUI还需要一个ui界面的产品,就要去修改所有具体工厂类和抽象工厂类,使得横向扩展***( 增加一些相同产品族的产品 )*** 很容易,但是纵向扩展***( 就是去改变原有的产品族结构 ,或者工厂构造方法结构)。***

原型模式(Prototype Pattern)

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

它由3部分构成:

  • 抽象原型类:提供一个clone方法供子类实现。( jdk提供了一个Cloneable接口,并实现了浅拷贝,是在Object实现的浅拷贝,但是如果你直接调用super.clone()会报错,必须引入Cloneable接口后,才有权限调用Object的clone()方法 )。
  • 具体原型类:实现抽象的clone方法。
  • 访问类:使用具体原型类中的clone方法来复制一个新的对象。

浅克隆:Object提供的clone方法就是浅克隆。创建一个新对象,对于普通类型的属性直接复制赋值,而面对引用类型的属性它拷贝的是地址,而不是重新去克隆值,去创建新的属性。

深克隆:不仅普通类型会赋值,引用类型也会创建然后赋值,引用类型其中的引用类型这个类属性管理的所有引用类型,都会重新去开辟内存空间然后赋值。可以用序列化再反序列化的方式去进行深克隆。

这里有一个十分细节的地方:面对String,Integer这种属性,实际上他们也是引用地址的,但为什么,改变复制类不会对被复制类产生影响呢,这是因为这种包装类,是不可变类,改变值的时候直接创建一个新的引用。

建造者模式(Builder Pattern)

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示;

  • 产品类(Product):通常是实现了模板方法模式,也就是有模板方法和基本方法。
  • 抽象建造类(Abstract Builder):规范产品的组建,一般是由子类实现。
  • 具体建造类(ConcreteBuilder):实现抽象类定义的所有方法,并且返回一个组建好的对象。
  • 指挥者类(Director):负责安排已有模块的顺序,然后告诉Builder开始建造。

这段代码省掉了抽象建造类,使用静态内部类方式,影响不大。

java 复制代码
public class Car {  
    private String engine;  
    private String wheels;  
  
    // 私有构造函数,防止外部直接实例化  
    private Car() {}  
  
    // 公开的getter方法  
    public String getEngine() {  
        return engine;  
    }  
  
    public String getWheels() {  
        return wheels;  
    }  
  
    // 静态内部类,作为建造者  
    public static class Builder {  
        private Car car;  
  
        public Builder() {  
            car = new Car();  
        }  
  
        public Builder withEngine(String engine) {  
            car.engine = engine;  
            return this; // 支持链式调用  
        }  
  
        public Builder withWheels(String wheels) {  
            car.wheels = wheels;  
            return this;  
        }  
  
        // 建造完成,返回产品  
        public Car build() {  
            return car;  
        }  
    }  
}
//构建者,也可以是调用方(用户)
public class Director {  
    public Car getCar(){  
        Car myCar = new Car.Builder()  
                .withEngine("V8")  
                .withWheels("Alloy")  
                .build();  
  
        System.out.println("Engine: " + myCar.getEngine());  
        System.out.println("Wheels: " + myCar.getWheels());  
        return myCar;
    }  
}

建造者模式的优点

  1. 使用建造者模式可以使客户端不必知道产品内部组成的细节。
  2. 实现建造者类之间是相互独立的,对系统的扩展非常有利,可以直接去改变指挥者类即可,不需要改变原有的类。
  3. 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他模块产生任何影响。

建造者模式的缺点

建造者模式所要建造的产品对象如果差异过大,就不太建议使用,比如造一个汽车和造一个电脑,api几乎没有重叠,导致复杂性没有降低,逻辑没有简化,重用性也没有增强,也更不符合单一职责原则。

结构性模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足"合成复用原则",所以对象结构型模式比类结构型模式具有更大的灵活性。

代理模式(Proxy Pattern)

为其他对象提供一种代理以控制对这个对象的访问。

  • 抽象主题(Subject):声明抽象的业务方法。
  • 真实主题(RealSubject):实现业务方法,是业务逻辑的具体执行者。
  • 代理(Proxy):它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且对真实主题角色进行增强。
java 复制代码
public interface Subject{
    public void request();
}    

public class RealSubject implements Subject{
    pubilic void request(){
        //业务逻辑处理
    }
}

public class Proxy implements Subject{
    private Subject subject = null;
    //默认被代理类
    public Proxy(){
        this.subject = new RealSubject();
    }    
    //通过构造参数传递代理者
    public Proxy(...){
        //...
    }
    public void request(){
        this.before();
        this.subject.request();
        this.after();
    }
    public void before(){

    }
    public void after(){

    }
}

JDK动态代理

jdk动态代理使用Proxy.newProxyInstance(ClassLoader,Class<?>[],Invocationhandler)实现。

  • ClassLoader loader:类加载器,用于加载目标对象获取类加载器,一般使用 对象.getClass().getClassLoader() 方式实现。
  • Class<?>[]:代理类实现的接口的字节码对象,一般使用 对象.getClass().getInterfaces()。
  • Invocationhandler h:代理对象调用的处理逻辑。其中又有三个参数,分别是Object proxy,就是返回的代理对象,Method method,就是要代理的方法,Object[] args ,就是方法所使用的参数。
java 复制代码
    GG g = new G();
    GG instance = (GG) Proxy.newProxyInstance(g.getClass().getClassLoader()
            , g.getClass().getInterfaces()
            , (proxy, method, args1) -> {
                System.out.println("before");
                Object o = method.invoke(g, args);
                System.out.println("after");
                return o;
            });
    instance.fun();

Cglib动态代理

看参数也能知道,创建代理对象时传递了对象的接口数组,代理类也实现了这个接口,与被代理类属于兄弟的关系。若没有接口,就无法进行强转;若有接口但被代理类中想要被代理的方法在接口中没有相应的抽象方法时,就导致代理类里也无法对该方法进行代理。

然后就出现了Cglib,代理类和被代理类之间属于父子关系,就不需要接口的实现即可。主要是创建代理增强生成类然后指定要代理的类,和处理代理逻辑的类(需要实现MethodInterceptor,因为MethodInterceptor又单实现了Callback接口,据语义知这是一个回调接口

  • Object o:代理对象
  • Method method:真实对象中的方法的Method实例
  • Object[] args:实际参数
  • MethodProxy method:代理对象中方法的method实例
java 复制代码
main() {
    Enhancer enhancer = new Enhancer();
    // 设置被代理类的类对象
    enhancer.setSuperclass(O.class);
    // 设置回调,即代理类要调用的方法
    enhancer.setCallback(new Factory());
    O obj = (O) enhancer.create();
    obj.fun();
}
public class Factory implements MethodInterceptor {
    
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Before method " + method.getName());
        Object result = methodProxy.invokeSuper(o, args); // 调用原始方法
        System.out.println("After method " + method.getName());

        return result;
    }
}

注意 :被代理类不能是final类,因为他需要被继承。

代理模式的优点

  1. 职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
  2. 高扩展性:具体主题角色是随时都会发生变化的,只要他实现了接口,甭管它如何变化,都逃不过接口的手掌,那我们的代理类完全就可以在不做任何修改的情况下使用。cglib甚至都不需要接口就可以实现这种功能。

代理模式的缺点

可能会增加系统的复杂度。

适配器模式( Adapter Pattern**)**

将一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够一起工作。适配器模式又叫做变压器模式,也叫做包装模式,但是包装模式可不止一个,还包括了装饰模式。适配器模式拥有以下成员:

  • 目标对象(Target):该角色定义把其他类转换为何种接口,也就是我们的期望接口。
  • 源角色(Adaptee):你想把谁转换成目标角色,这个谁就是源角色,它是已经存在的、运行良好的类或对象。
  • 适配器角色(Adapter):适配器模式的核心角色,其他两个角色都是已经存在的角色,而适配器角色是需要新建立的,它的职责非常简单:把源角色转换为目标对象,用继承或者类关联的方式转换。

Adapter与Target用继承(类)或实现(接口)方式连接,然后重写方法, Adapter与Adaptee用聚合,继承(由于单继承,只能有一个选取继承方式连接)的方式。

注意:你有动机修改一个已经投产中的接口时,适配器模式可能是最适合你的模式,比如要使用一个已有的类,但这个类又不符合系统的接口,就可以使用适配器模式。

java 复制代码
// 目标抽象类
public interface ThreeHoleSocket {  
    void powerOn();  
}
// 目标实现类
public class RealThreeHoleSocket implements ThreeHoleSocket {  
    @Override  
    public void powerOn() {  
        System.out.println("Three-hole socket is powering on the device.");  
    }  
}
// 被适配的抽象类
public interface TwoHoleSocket {  
    void powerOnTwoHole();  
}
// 被适配的具体实现类
public class OldTwoHoleSocket implements TwoHoleSocket {  
    @Override  
    public void powerOnTwoHole() {  
        System.out.println("Two-hole socket is powering on (old style).");  
    }  
}
// 适配器,这里使用实现,目标抽象类,聚合被适配的抽象类的方式实现(也可以使用继承)
public class SocketAdapter implements ThreeHoleSocket {  
    private TwoHoleSocket twoHoleSocket;  
  
    public SocketAdapter(TwoHoleSocket twoHoleSocket) {  
        this.twoHoleSocket = twoHoleSocket;  
    }  
  
    @Override  
    public void powerOn() {  
        // 适配过程:调用两孔插座的powerOnTwoHole方法,并模拟转换为三孔插座的效果  
        twoHoleSocket.powerOnTwoHole();  
        System.out.println("Adapter converts power to three-hole format.");  
    }  
}
// 调用
public class Client {  
    public static void main(String[] args) {  
        TwoHoleSocket oldSocket = new OldTwoHoleSocket();  
        ThreeHoleSocket adapter = new SocketAdapter(oldSocket);  
  
        // 使用适配器将电器设备连接到老式插座  
        adapter.powerOn();  
    }  
}

装饰模式(Decoration Pattern)

动态地给一个对象添加一些额外的职责(增加功能)。就增加功能来说,装饰模式相比于生成子类更加灵活。

  • 抽象构件角色(AbstractComponent):定义一个抽象去让装饰对象和被装饰对象对实现它
  • 具体构件角色(ConcreteComponent):是被装饰的对象
  • 抽象装饰角色(AbstractDecorator):定义被装饰对象的抽象,保证开闭原则,可以灵活规定多种装饰实现(省略将不影响模式逻辑)
  • 具体装饰角色(ConcreteDecorator):实现抽象,具体实现装饰的逻辑
java 复制代码
// 抽象构件角色
public interface Coffee {  
    double cost();  
    String getDescription();  
}
// 具体构件角色
public class SimpleCoffee implements Coffee {  
    @Override  
    public double cost() {  
        return 10.0;  
    }  
  
    @Override  
    public String getDescription() {  
        return "Simple Coffee";  
    }  
}
// 抽象装饰角色
public abstract class CoffeeDecorator implements Coffee {  
    protected Coffee decoratedCoffee;  
  
    public CoffeeDecorator(Coffee decoratedCoffee) {  
        this.decoratedCoffee = decoratedCoffee;  
    }  
  
    @Override  
    public double cost() {  
        return decoratedCoffee.cost();  
    }  
  
    @Override  
    public String getDescription() {  
        return decoratedCoffee.getDescription();  
    }  
}
// 具体装饰角色
public class MilkCoffeeDecorator extends CoffeeDecorator {  
    public MilkCoffeeDecorator(Coffee decoratedCoffee) {  
        super(decoratedCoffee);  
    }  
  
    @Override  
    public double cost() {  
        return super.cost() + 2.0;  
    }  
  
    @Override  
    public String getDescription() {  
        return super.getDescription() + ", Milk";  
    }  
}  
  
public class SugarCoffeeDecorator extends CoffeeDecorator {  
    public SugarCoffeeDecorator(Coffee decoratedCoffee) {  
        super(decoratedCoffee);  
    }  
  
    @Override  
    public double cost() {  
        return super.cost() + 1.5;  
    }  
  
    @Override  
    public String getDescription() {  
        return super.getDescription() + ", Sugar";  
    }  
}
// 调用
public class DecoratorPatternDemo {  
    public static void main(String[] args) {  
        Coffee coffee = new SimpleCoffee();  
        coffee = new MilkCoffeeDecorator(coffee);  
        coffee = new SugarCoffeeDecorator(coffee);  
  
        System.out.println(coffee.getDescription() + " $" + coffee.cost());  
    }  
}

这个逻辑的例子就是递归包装,抽象装饰类里聚合着抽象构件,每包装一层,之前的对象都会被设置到变量的位置,然后cost方法直接重写分为两部分 = 现在的对象.getPrice() + 变量上的对象.cost(去递归调用),就实现了无限包装。

装饰模式的优点

装饰类和被装饰类可以独立发展,而不会互相耦合。换句话说,Component类无须知道Decorator,Decorator类是从外部来拓展Component类的功能,而Decorator也不用知道具体的构件。

装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。

装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如此。

装饰模式的缺点

对于装饰模式记住一点就足够了:多层的装饰是比较复杂的。为什么会复杂呢?你想想看,就像剥洋葱一样,你剥到了最后才发现是最里面的装饰出现了问题,想象一下工作量吧,因此尽量减少装饰类的数量,以便降低系统复杂度。

桥接模式(Bridge Pattern)

桥梁模式也叫做桥接模式,是一个比较简单的模式,定义: 将抽象和实现解耦,使得两者可以独立地变化。

  • 抽象化角色(Abstraction):定义出该角色的行为,同时保存一个对实现化角色的引用。
  • 扩展抽象化角色(Refined Abstraction):对抽象化角色给予实现
  • 实现化角色(Implementor):定义角色必需的行为和属性
  • 具体实现化角色(ConcreteImplementor):给出实现化角色抽象的具体实现。
java 复制代码
// 实现化角色的抽象,被调用
public interface Implementor {
    void doSomething();
}
// 实现化角色的实现,被调用
public class ConcreteImplementor implements Implementor{
    @Override
    public void doSomething() {

    }
}
// 抽象化角色
public abstract class Abstraction {
    protected Implementor imp;

    public Abstraction(Implementor imp) {
        this.imp = imp;
    }
    public abstract void request();
}

public class RefinedAbstraction extends Abstraction{
    public RefinedAbstraction(Implementor imp) {
        super(imp);
    }
    @Override
    public void request(){
        imp.doSomething();
    }
}

public class Client {
    public static void main(String[] args) {
        Abstraction abs = new RefinedAbstraction(new ConcreteImplementor());
        abs.request();
    }
}

桥接模式优点

在该模式下,实现可以不受抽象的约束,不用再绑定在一个固定的抽象层次上。拥有优秀的扩充能力,客户不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装。

门面模式(Facade Pattern)

门面模式也叫外观模式,是一种比较常用的封装模式。定义为 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更容易使用。

  • 门面角色(Facade):客户端可以调用这个角色的方法。此角色知晓子系统的所有功能和责任。一般情况下, 本角色会将所有从客户端发来的请求委派到相应的子系统去,也就说该角色没有实际的业务 逻辑,只是一个委托类。
  • 子系统角色(sub System):可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。子系统并不知道门面的存在。对于子系统而言,门面仅仅是另外一个客户端而已。

这种模式十分多变,实现非常自由,没有太模板化的示例,随便举个栗子

java 复制代码
public class ClassA {
    public void doSomethingA(){}
}

public class ClassB {
    public void doSomethingB(){}
}

public class ClassC {
    public void doSomethingC(){}
}
public class Facade {
    private ClassA a = new ClassA();
    private ClassB b = new ClassB();
    private ClassC c = new ClassC();
    public void methodA() { this.a.doSomethingA(); }
    public void methodB() { this.b.doSomethingB(); }
    public void methodC() { this.c.doSomethingC(); }
} 

门面模式的优点

减少系统的相互依赖,如果我们不使用门面模式,外界直接访问到子系统内部,耦合性太大,这种强依赖是系统设计不能接受额,门面模式就很好的解决了这个问题。

提高了灵活性,依赖减少了,灵活性自然提高了,不管子系统内部如何变化,只要不影响到门面对象,任你自由活动。

提高安全性,想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的犯法,你休想访问到。

门面模式的缺点

门面模式最大的缺点就是不符合开闭原则,对修改关闭,对扩展开放。当投产中出现错误只能去修改门面模式,没有办法完全遵循开闭原则。

组合模式(Composite Pattern)

组合模式也叫合成模式,有时又叫做部分-整体(Part-Whole),主要是用来描述部分与整体的关系,定义是:将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

  • 抽象构件(Component):定义参加组合对象的公有方法和属性,可以定义一些默认的行为或属性。
  • 树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶节点形成一个树形结构。
  • 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

这个例子是透明组合模式,用来组合使用的方法放到抽象类中,可以使用抽象编程。如果把add,remove,这种都放到实现类里叫做安全组合模式,无法使用抽象编程。

java 复制代码
//菜单组件  不管是菜单还是菜单项,都应该继承该类
public abstract class MenuComponent {

    protected String name;
    protected int level;

    //添加菜单
    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    //移除菜单
    public void remove(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    //获取指定的子菜单
    public MenuComponent getChild(int i){
        throw new UnsupportedOperationException();
    }

    //获取菜单名称
    public String getName(){
        return name;
    }

    public void print(){
        throw new UnsupportedOperationException();
    }
}

public class Menu extends MenuComponent {

    private List<MenuComponent> menuComponentList;

    public Menu(String name,int level){
        this.level = level;
        this.name = name;
        menuComponentList = new ArrayList<MenuComponent>();
    }

    @Override
    public void add(MenuComponent menuComponent) {
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int i) {
        return menuComponentList.get(i);
    }

    @Override
    public void print() {

        for (int i = 1; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
        for (MenuComponent menuComponent : menuComponentList) {
            menuComponent.print();
        }
    }
}
public class MenuItem extends MenuComponent {

    public MenuItem(String name,int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
        for (int i = 1; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
    }
}

组合模式的优点

一颗树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。

客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。

在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合"开闭原则"。

组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

享元模式(Flyweight Pattern)

享元模式存在两种状态,内部状态是对象可共享出来的信息,储存在享元对象内部并且不会随环境改变而改变,如例子中的id、postAddress等,它们可以作为一个对象的动态附加信息,不必直接储存在具体某个对象中,属于可以共享的部分;外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态。

  • 抽象享元角色(Flyweight):简单来说就是一个产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。
  • 具体享元角色(Concrete Flyweight):对抽象享元进行了实现,该角色中需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同时修改了外部状态,这是绝对不允许的。
  • 非享元角色(Unsharable Flyweight):不存在外部状态或者安全要求(如线程安全)不能够使用共享技术的对象,该对象一般不会出现在享元工厂中。
  • 享元工厂(Flyweight Factory):职责非常简单,就是构造一个池容器,同时提供从池中获得对象的方法。

享元模式的目的在于运用共享技术,使得一些细粒度的对象可以共享,我们的设计确实也应该这样,多使用细粒度的对象,便于重用或重构。

java 复制代码
public abstract class Flyweight {
    // 外部状态
    private final String Extrinsic;
    // 内部状态
    private String intrinsic;

    // 要求享元角色必须接受外部状态
    public Flyweight(String extrinsic) {
        Extrinsic = extrinsic;
    }

    //定义业务操作
    public abstract void operate();

    //内部状态的getter/setter
    public String getIntrinsic() {
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }
}

public class ConcreteFlyweight1 extends Flyweight{
    // 接受外部状态
    public ConcreteFlyweight1(String extrinsic) {
        super(extrinsic);
    }


    @Override
    public void operate() {

    }
}


public class ConcreteFlyweight2 extends Flyweight{
    // 接受外部状态
    public ConcreteFlyweight2(String extrinsic) {
        super(extrinsic);
    }


    @Override
    public void operate() {

    }
}

public class FlyweightFactory {
    private static HashMap<String,Flyweight> pool = new HashMap<>();
    public static Flyweight getFlyWeight(String Extrinsic){
        Flyweight flyweight = null;
        if(pool.containsKey(Extrinsic)){
            flyweight = pool.get(Extrinsic);
        }else{
            flyweight = new ConcreteFlyweight1(Extrinsic);
            pool.put(Extrinsic,flyweight);
        }
        return flyweight;
    }
}

享元模式优点

享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象,降低程序内存的占用,增强程序的性能。

享元模式缺点

提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。

行为型模式

行为型模式用于表述程序在运行时的流程控制,即描述的是多个类或多个对象之间共同协作完成单个类或对象无法完成的功能。分为两种,类行为模式和对象行为模式,除了模板方法模式和解释器模式是类行为模式,其他全为对象行为型模式。

模板方法模式(Template Method Pattern)

定义一个逻辑的骨架,具体详细的实现由子类完成。

  • 抽象类(Abstract Class):给出程序逻辑的一个骨架轮廓,由一个模板方法和若干基本方法构成。 模板方法 定义了如何去组装这些基本方法,这就相当于定义了模板和骨架,子类只负责去填冲这个骨架;抽象方法 是可以让子类去具体实现它;具体方法 是模板中已经定义好的,子类可以去重写它;钩子方法在抽象类里实现,一般用于判断逻辑返回boolean。
  • 具体子类(Concrete Class):实现抽象类所定义的抽象。
java 复制代码
// 模板类  
public abstract class CoffeeTemplate {  
    // 模板方法,定义了冲泡咖啡的算法骨架  
    final void prepareRecipe() {  
        boilWater();  
        brewCoffeeGrinds();  
        pourInCup();  
        addCondiments();  
    }  
  
    // 抽象方法,由子类实现  
    abstract void boilWater();  
  
    // 抽象方法,由子类实现,这里为了演示,我们改用brewCoffeeGrinds代替brewCoffee  
    abstract void brewCoffeeGrinds();  
  
    // 钩子方法,默认什么也不做,子类可以选择实现它  
    void addCondiments() {}  
  
    // 抽象方法,由子类实现  
    abstract void pourInCup();  
}  
  
// 具体实现类  
public class DarkRoast extends CoffeeTemplate {  
    @Override  
    void boilWater() {  
        System.out.println("Boiling water");  
    }  
  
    @Override  
    void brewCoffeeGrinds() {  
        System.out.println("Dripping Dark Roast through filter");  
    }  
  
    @Override  
    void pourInCup() {  
        System.out.println("Pouring into cup");  
    }  
  
    // 可选实现钩子方法  
    @Override  
    void addCondiments() {  
        System.out.println("Adding Sugar and Milk");  
    }  
}  
  
// 另一个具体实现类  
public class Espresso extends CoffeeTemplate {  
    @Override  
    void boilWater() {  
        System.out.println("Boiling water in a pressure chamber");  
    }  
  
    @Override  
    void brewCoffeeGrinds() {  
        System.out.println("Dripping very finely ground coffee under pressure");  
    }  
  
    @Override  
    void pourInCup() {  
        System.out.println("Pouring into a small cup");  
    }  
  
    // 不实现钩子方法,保持默认行为  
}  
  
// 使用模板模式的客户端代码  
public class CoffeeMaker {  
    public static void main(String[] args) {  
        CoffeeTemplate coffee1 = new DarkRoast();  
        System.out.println("Making a cup of Dark Roast:");  
        coffee1.prepareRecipe();  
  
        CoffeeTemplate coffee2 = new Espresso();  
        System.out.println("\nMaking a cup of Espresso:");  
        coffee2.prepareRecipe();  
    }  
}

模板方法模式的优点

提高了代码复用性,将相同的代码定义在父类里。

通过父类调用子类的具体行为,创建新的子类,扩展不同的功能,符合开闭原则。

模板方法模式的缺点

缺点不太致命,面对新的需求,虽然不会引起类爆炸式增多,但面对不同的需求都要新建一个子类去实现模板。

策略模式(Strategy Pattern)

  • 抽象策略角色(Strategy):策略家族的抽象,定义每个策略或算法必须具有的方法和属性
  • 具体策略角色(ConcreteStrategy):实现抽象策略中的操作,该类含有具体的算法或逻辑
  • 环境(Context):持有一个策略类的引用,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,进行封装,供高层模块直接调用
java 复制代码
public interface Strategy {
    /**
     * 策略模式的运算法则
     */
    public void doSomething();
}
// 策略具体实现
public class ConcreteStrategy1 implements Strategy{
    @Override
    public void doSomething() {
        System.out.println("具体策略1的运算法则");
    }
}
public class ConcreteStrategy2 implements Strategy{
    @Override
    public void doSomething() {
        System.out.println("具体策略2的运算法则");
    }
}
public class Context {
    // 抽象策略
    private Strategy strategy;

    /**
     * 构造函数设置具体策略
     * @param strategy
     */
    public Context(Strategy strategy){
        this.strategy = strategy;
    }

    /**
     * 封装后的策略方法
     */
    public void doAnything(){
        this.strategy.doSomething();
    }
}

策略模式的优点

算法可以自由切换, 这是策略模式本身定义的,只要实现抽象策略,他就成为了策略家族的一个成员,通过封装角色对其进行封装,保证对外提供"可自由切换"的策略。

避免使用多重条件判断, 如果没有策略模式,一会用A策略一会用B策略,要使用多重条件语句,再策略模式里可以由别的模块封装

扩展性良好,在现有的系统中增加一个策略实现一个接口就可以了。

策略模式的缺点

层模块必须知道有哪些策略,然后才能决定使用哪一种策略,这与迪米特法则是相违背的。我只是想使用一个策略,我为什么要了解这个策略呢,但是我们可以用其他模式来修正这个缺陷。若策略过多,需要使用享元模式,单例等去改善对象的大量创建。每个策略都对应一个类,可能会导致类的数量膨胀。

命令模式(Command Pattern)

  • 抽象命令角色(Command:):需要执行的所有命令都在这里声明抽象。
  • 具体命令角色(Concrete Command):具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 接收者角色(Receiver):接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者角色(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
java 复制代码
// 抽象命令角色
public interface Command {
    void execute(); //只需要定义一个统一的执行方法
}
// 具体命令角色
public class ConcreteCommand{
    protected final Receiver1 receiver1;
    protected final Receiver2 receiver2;

    //也可以把Receiver实现类全部列出来,但是当类多的时候,会占用过多资源,浪费效率
    public Command(Receiver receiver){
        this.receiver = receiver;
    }
    public void execute() {
        // 自定义命令操作
        receiver1.add1();
        receiver2.add2();
    }
}
// 接收者角色
public class receiver1 {
    public void add1() { System.out.println("1"); }
}
public class receiver2 {
    public void add2() { System.out.println("2"); }
}
// 调用者对象
class Invoker {
    // 通常会加上一些额外的处理,比如用集合存储Command,完成命令的存储
    private Command command;

    public Invoker(Command command) {
        this.command = command;
    }

    public void executeCommand() {
        command.execute();
    }
}

public class Client {
    public static void main(String[] args) {
        Command command = new ConcreteCommand();
        Invoker invoker = new Invoker(command);
        invoker.executeCommand();
    }
}

命令模式的优点

调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法即可,不需要了解到底是哪个接收者执行。

Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。

命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类膨胀问题。

命令模式的缺点

假如有N个命令,问题就会出现,类会膨胀的特别大。

命令模式的经典场景

在JDK的线程重就使用了命令模式,Runnable实际上就是抽象命令角色 ,然后我们可以选择实现Runnable接口,实现自己的业务逻辑,这叫做具体命令角色 。然后Thread继承了Runnable,这个功能是重写了一下Runnable的run方法(实际上还是调用参数上的Runnable的run),更多的是起一个标志的作用,标识Thread线程类是可运行的,然后在start()方法中调用了一个native方法start0()方法,里面新开了一个线程去执行一个Runnable类型的成员变量target的run方法,所以Thread叫做这个系统的调用者角色 。而具体命令角色中的成员变量就是接收者角色。各个角色的定义没有那么宽泛,一段方法也可以叫做一个角色。

责任链模式(Chain of Responsibility Pattern)

使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止,也可以完成不同对象的不同处理。

  • 抽象处理角色(Handler) :定义一个处理请求的接口,包含抽象处理方法和一个后续连接。
  • 具体处理角色(Concrete Handler):对抽象进行实现,因为是一层接一层,所以还需要完成标识层级等属性来完成是否能够处理和处理的逻辑。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

责任链模式的优点

  1. 降低了对象之间的耦合度该模式降低了请求发送者和接收者的耦合度。
  2. 增强了系统的可扩展性可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

责任链模式的缺点

责任链有两个非常显著的缺点:一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。二是调试很不方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑比较复杂。

状态模式(State Pattern)

当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。

  • 抽象状态角色(State):负责对象状态定义,并且封装环境角色以实现状态切换。
  • 具体状态(Concrete State)角色:每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
  • 环境角色(Context):定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理,并且负责具体状态的切换。

环境类里要有抽象状态角色的成员变量,因为要引用使用它每个状态的方法,抽象状态角色里也要有环境角色的成员变量,因为具体状态角色的方法有可能需要去改变状态。

java 复制代码
// 抽象状态角色
public abstract class State {
    protected Context context;
    public void setContext(Context context) {
        this.context = context;
    }
    public abstract void handle1();
    public abstract void handle2();
}
// 具体状态角色
public class ConcreteState1 extends State{
    @Override
    public void handle1() {
        //本状态下必须处理的逻辑
    }
    @Override
    public void handle2() {
        super.context.setCurrentState(Context.STATE2);
        super.context.handle2();
    }
}
public class ConcreteState2 extends State{
    @Override
    public void handle1() {
        super.context.setCurrentState(Context.STATE1);
        super.context.handle1();
    }
    @Override
    public void handle2() {
        //本状态下必须处理的逻辑
    }
}
// 环境角色
public class Context {
    public final static State STATE1 = new ConcreteState1();
    public final static State STATE2 = new ConcreteState2();
    private State currentState;

    public State getCurrentState() {
        return currentState;
    }

    public void setCurrentState(State currentState) {
        this.currentState = currentState;
        //切换状态
        this.currentState.setContext(this);
    }

    //行为委托
    public void handle1() {
        this.currentState.handle1();
    }

    public void handle2() {
        this.currentState.handle2();
    }
}
public class Client {
    public static void main(String[] args) {
        // 定义环境角色
        Context context = new Context();
        // 初始化状态
        context.setCurrentState(new ConcreteState1());
        //行为执行
        context.handle1();
        context.handle2();
    }
}

状态模式的优点

避免了过多的switch...case或者if...else语句的使用,避免了程序的复杂性,提高系统的可维护性。

很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。

这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换,符合迪米特发展。

状态模式的缺点

子类会太多,也就是类膨胀;开闭模式没有完全遵循;实现复杂。

观察者模式(Observer Pattern)

观察者模式也叫做发布订阅模式,它是一个在项目中经常使用的模式,定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

  • 抽象被观察者(Subject):必须能够动态地增加、取消观察者。仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。
  • 具体被观察者(ConcreteSubject):定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • 抽象观察者(Observer):是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • 具体观察者(ConcrereObserver):每个观察在接收到消息后的处理反应是不同的,各个观察者有自己的处理逻辑,实现抽象观察者定义的更新接口。
java 复制代码
// 抽象被观察者角色
public abstract class Subject {
    // 这里其实应该把列表放到各个子类里,不能我关注两个不同的subject,调用一个的触发方法,把所有的都给触发掉
    private Vector<Observer> observerVector = new Vector<>();
    public void addObserver(Observer o){
        this.observerVector.add(o);
    }
    public void delObserver(Observer o){
        this.observerVector.remove(o);
    }
    public void notifyObservers(){
        for (Observer o : this.observerVector) {
            o.update();
        }
    }
}
// 具体被观察者角色
public class ConcreteSubject extends Subject{
    public void doSomething(){
        super.notifyObservers();
    }
}
// 抽象观察者角色
public interface Observer {
    void update();
}
// 具体观察者角色
public class ConcreteObserver implements Observer{
    @Override
    public void update() {
        System.out.println("接收到信息,并进行处理!");
    }
}

观察者模式的优点

如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在java中都已经实现的抽象层级的定义。

根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑呢?比如我们去打猎,打死了一头母鹿,有三个月幼崽,因为失去了母鹿饿死,然后又因为母鹿的尸体,老鹰开始争抢,然后导致残杀。这就是一个触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。

观察者模式的缺点

观察者模式需要考虑一下开发效率和运行效率的问题,一个被观察者,多个被观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑异步的方式。

中介者模式(Mediator Pattern)

又叫调停模式,用一个中介对象封装一系列的对象交互,中介者使各对象不需要显式地相互作用,从而使其耦合松散,而且可以独立地改变它们的交互。

  • 抽象中介者角色(Mediator):抽象中介者角色定义统一的接口,用于各同事角色之间的通信,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者角色(Concrete Mediator):具体中介者角色通过协调各同事之间实现协作行为(一般定义一个 List 来管理同事对象)协调各个同事角色之间的交互关系,因此它依赖于各个同事角色。
  • 抽象同事类角色(Colleague):定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色::每个同事角色都知道中介者角色,而且与其他同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有依赖;第二种必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)。
java 复制代码
// 抽象中介者
public abstract class Mediator {
    // 定义同事类
    protected ConcreteColleague1 c1;
    protected ConcreteColleague2 c2;

    // 通过setter方法,注入同事类
    public void setC1(ConcreteColleague1 c){ this.c1 = c; }
    public void setC2(ConcreteColleague2 c){ this.c2 = c; }

    // 依赖处理的方法,也就是联络的方法
    public abstract void doSometing();
}
// 具体实现中介者
public ConcreteMediator extends Mediator {
    @Override
    public void doSometing(){
        super.c1.selfMethod();
        super.c2.selfMethod();
    }  
}
// 抽象同事角色
public abstract class Colleague {
    protected Mediator mediator;
    public Colleague(Mediator _mediator){
        this.mediator = _mediator;   
     }
}
// 具体实现同事角色
public ConcreteColleague1 extends Colleague{
    public ConcreteColleague1(Mediator _mediator){
        super(_mediator);
    }
    //自发行为,没有任何依赖
    public Object selfMethod(){}

    //依赖方法,委托给中介者
    public void depMethod(){
        super.mediator.doSomething();
    }
}
public ConcreteColleague2 extends Colleague{
    public ConcreteColleague2(Mediator _mediator){
        super(_mediator);
    }
    public Object selfMethod(){}
    public void depMethod(){
        super.mediator.doSomething();
    }
}

这个的实现只是遵从了框架而已,如果是具体业务的话,一般调用中介类里的沟通方法时,还需要传一些标识性的参数信息,比如this关键字,把自己的对象传过去,来标识是谁传过来的。

中介者模式的优点

中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同时类只依赖中介类,减少了依赖,当然同事也降低了类间的耦合。

中介者模式的缺点

中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

迭代器模式(Iterator Pattern)

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
java 复制代码
public interface Iterator {
    Object next();
    boolean hasNext();
    boolean remove();
}

public class ConcreteIterator implements Iterator {
    public int cursor = 0;
    private Vector vector;

    public ConcreteIterator(Vector _vector) {
        this.vector = _vector;
    }

    @Override
    public Object next() {
        Object result;
        if (this.hasNext()) {
            result = this.vector.get(this.cursor++);
        } else {
            result = null;
        }
        return result;
    }

    @Override
    public boolean hasNext() {
        return this.cursor != this.vector.size();
    }

    @Override
    public boolean remove() {
        this.vector.remove(this.cursor);
        return true;
    }
}

public interface Aggregate {
    void add(Object object);
    void remove(Object object);
    Iterator iterator();
}


public class Client {
    public static void main(String[] args) {
        Aggregate aggregate = new ConcreteAggregate();
        aggregate.add("abc");
        aggregate.add("aaa");
        aggregate.add("1234");
        Iterator iterator = aggregate.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

访问者模式(Visitor Pattern)

封装一些作用与某种数据结构中的个元素的操作,它可以在不改变数据结构的前提下定义作用域这些元素的新的操作。

  • 抽象访问者角色(Visitor):定义了对每一个元素访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者角色(ConcreteVisitor):给出对每一个元素类访问时所产生的具体行为,影响访问者访问到一个类后该怎么干,要做什么事情。
  • 抽象元素角色(Element):定义了一个接受访问者的accept方法,其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素角色(ConcreteElement): 实现accept方法,通常是visitor.visit(this),基本上都形成了一种规范了,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构角色(Object Structure):定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素,并且可以迭代这些元素,供访问者访问。
java 复制代码
// 抽象访问者角色
public interface Person {
    void feed(Cat cat);
    void feed(Dog dog);
}
// 具体访问者角色
public class Owner implements Person {

    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食狗");
    }
}

public class Someone implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("其他人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("其他人喂食狗");
    }
}
// 抽象元素角色
public interface Animal {
    void accept(Person person);
}
// 具体元素角色
public class Dog implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,汪汪汪!!!");
    }
}
public class Cat implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,喵喵喵!!!");
    }
}
// 对象结构
public class Home {
    private List<Animal> nodeList = new ArrayList<Animal>();

    public void action(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }

    //添加操作
    public void add(Animal animal) {
        nodeList.add(animal);
    }
}
// 测试类
public class Client {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
        home.add(new Cat());

        Owner owner = new Owner();
        home.action(owner);

        Someone someone = new Someone();
        home.action(someone);
    }
}

主要就三部分,元素,访问者和结构,元素主要是直接被调用,然后元素被调用的方法里有访问者对象,所以它可以(也一般都是)调用访问者对象的方法;然后结构就根据业务具体封装等,栗子中就实现了一个,批量调用访问的功能。

访问器模式的优点

符合单一职责原则,具体元素角色也就是Employee抽象类的两个子类负责数据的加载,而Visitor类则负责报表的展现,两个不同的职责非常明确地分离开来,各自演绎变化。

优秀的扩展性,由于职责分开,继续增加对数据的操作是非常快捷的。例如,现在要增加一个访问对象,直接在Visitor中增加一个方法,传递数据后进行整理打印。

灵活性非常高

访问器模式的缺点

具体元素对访问者公布细节,访问者要访问一个类就必然要求这个类公布一些方法和数据,也就是说访问者关注了其他类的内部细节,这是迪米特法则不建议的。

具体元素变更比较困难,具体元素角色的增加、删除、修改都是比较困难的,就上面那个例子,要是增加一个成员变量,Visitor就需要修改,如果多个Vistor就会很麻烦

违背了依赖倒置原则, 访问者依赖的是具体元素,而不是抽象元素,这破坏了依赖倒置的原则,特别是在面向对象的编程中,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。

备忘录模式(Memento Pattern)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

  • 发起人角色(Originator):记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。

  • 备忘录角色(Memento):负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。

备忘录管理员角色(Caretaker):对备忘录进行管理、保存和提供备忘录

java 复制代码
// 发起人角色,就是具有备忘功能的角色
public class Originator {
    private String state = "";

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
    public Memento createMemento(){
        return new Memento(this.state);
    }
    public void restoreMemento(Memento memento){
        this.setState(memento.getState());
    }
}
// 备忘录角色,去实现备忘功能
public class Memento {
    private String state = "";

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
// 备忘录管理员角色,起一个管理,存储的功能
public class Caretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
// 测试
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(originator.createMemento());
        originator.restoreMemento(caretaker.getMemento());
    }
}

备忘录模式的注意事项

备忘录创建出来就要在 "最近" 的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理。

不要再频繁建立备份的场景中使用备忘录模式(比如for循环里),原因有二:一是控制不了备忘录建立的对象数量;二是大对象的建立是要消耗资源的,系统的性能需要考虑。因此,如果出现这样的代码,设计师就应该好好想想怎么修改架构了。

解释器模式

解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少,定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

  • 抽象解释器(AbstractExpression): 具体的解释任务由各个实现完成,具体的解释器分别由TerminalExpression和NonterminalExpreesion完成。
  • 终结符表达式(TerminalExpreesion):实现与文法中元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到例子中就是,表达式中每个终结符都在栈中产生了一个VarExpreesion对象。
  • 非终结符表达式(NonterminalExpression):文法中的每条规则对应于一个非终结表达式,具体到我们的例子就是加减法规则分别对应到AddExpression两个类。非终结符表达式根据业务逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
  • 环境对象(Context):具体到例子中就是用HashMap来代替的。
java 复制代码
// 抽象解释器
public abstract class Expression {
    public abstract Object interpreter(HashMap ctx);
}
// 非终结符表达式
public class NonTerminalExpression extends Expression{
    //每个非终结符表达式都会对其他表达式产生依赖
    public NonTerminalExpression(Expression... expression) {
    }

    @Override
    public Object interpreter(HashMap ctx) {
        //进行文法处理
        return null;
    }
}
// 终结符表达式
public class TerminalExpression extends Expression{
    @Override
    public Object interpreter(HashMap ctx) {
        return null;
    }
}

public class Client {
    public static void main(String[] args) {
        HashMap<Object, Object> ctx = new HashMap<>();
        Stack<Expression> stack = new Stack<>();
        for (; ; ) {
            // 语法判断,并产生递归调用
        }
        Expression exp = stack.pop();
        exp.interpreter(ctx);

    }
}

解释器模式的优点

解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。

解释器模式的缺点

  • 每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件。
  • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。
  • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦
相关推荐
王嘉俊9257 分钟前
设计模式--适配器模式:优雅解决接口不兼容问题
java·设计模式·适配器模式
王嘉俊9259 分钟前
设计模式--组合模式:统一处理树形结构的优雅设计
java·设计模式·组合模式
rongqing201910 分钟前
Google 智能体设计模式:多智能体协作
设计模式
李广坤15 小时前
状态模式(State Pattern)
设计模式
李广坤16 小时前
观察者模式(Observer Pattern)
设计模式
李广坤17 小时前
中介者模式(Mediator Pattern)
设计模式
李广坤17 小时前
迭代器模式(Iterator Pattern)
设计模式
李广坤18 小时前
解释器模式(Interpreter Pattern)
设计模式
阿无,21 小时前
java23种设计模式之前言
设计模式
Asort1 天前
JavaScript设计模式(八):组合模式(Composite)——构建灵活可扩展的树形对象结构
前端·javascript·设计模式