【知识点】设计模式

news2024/11/28 18:40:58

创建型

单例模式 Singleton:确保一个类只有一个实例,并提供该实例的全局访问点

使用一个私有构造方法、一个私有静态变量以及一个公有静态方法来实现。私有构造方法确保了不能通过构造方法来创建对象实例,只能通过公有静态方法返回唯一的私有静态变量。

懒汉式-线程不安全:私有静态变量被延迟实例化,在多线程环境下不安全,可能多次实例化


public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {}

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }

}    

饿汉式-线程安全:采取直接实例化的方式就不会产生线程不安全的问题


private static Singleton uniqueInstance = new Singleton();

懒汉式-线程安全:只需要对getUniqueInstance()方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次uniqueInstance。但是当一个线程进入后其他线程必须等待,即使对象已经被实例化,这会让阻塞时间过长,因此该方法有性能问题。


public static synchronized Singleton getUniqueInstance() {

    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

双重校验锁-线程安全:对象只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化部分的代码进行,只有当uniqueInstance没有被实例化时,才需要进行加锁。双重校验锁先判断uniqueInstance是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。


public static getUniqueInstance() {
    if (null == uniqueInstance) {
        synchronized(Singleton.class) {
            if (null == uniqueInstance) {
                uniqueInstance = new Singleton();
            }
        }
    }
}

uniqueInstance采用volatile关键字修饰也是很有必要的,uniqueInstance = new Singleton();这段代码其实是分三步执行

  1. uniqueInstance分配内存空间
  2. 初始化uniqueInstance
  3. uniqueInstance指向分配的内存地址

‼️ 由于JVM具有指令重排的特性,执行顺序有可能变成1>3>2指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行了13,此时T2调用getUniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但此时uniqueInstance还未被初始化。

使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行

静态内部类实现:当Singleton类被加载时,静态内部类SingletonHolder没有被加载进内存。只有当调用getUniqueInstance()方法从而触发SingletonHolder.INSTANCESingletonHolder才会被加载,此时初始化INSTANCE实例,并且JVM能确保INSTANCE只被实例化一次。

public class Singleton() {
    private Singleton(){}

    private static class SingletonHolder() {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTNACE;
    }
}

枚举实现:可以防止反射攻击。在其他实现中,通过setAccessible()方法可以将私有构造方法的访问级别设置为public,然后调用构造方法从而实例化对象,如果要防止这种攻击,需要在构造方法中添加防止多次实例化的代码。该实现是由JVM保证之后实例化一次,因此不会出现上述的反射攻击。该实现在多次序列化反序列化之后,不会得到多个实例。而其他实现需要使用transient修饰所有字段,并且实现序列化反序列化的方法。

public enum Singleton {

    INSTANCE;

    private String objName;

    public String getObjName(){
        return objName;
    }

    public void setObjName(String objName) {
        this.objName = objName;
    }
}

简单工厂 Simple Factory:在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口

简单工厂把实例化的操作单独放到一个类,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。

public class SimpleFactory {
    public Product createProduct(int type) {
        if (type == 1)
            return new ConcreateProduct1();
        else if (type == 2) 
            return new ConcreateProduct2();
        return new ConcreateProduct3();
    }
}

public class Client {
    public static void main(String[] args) {
        SimpleFactory f = new SimpleFactory();
        Product p = f.createProduct(1);
    }
}

工厂方法 Factory Method:定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。

在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。下图中,Factory有一个doSomething方法,这个方法需要用到一个产品对象,这个产品对象由factoryMethod方法创建。该方法是抽象的,需要由子类去实现。

public abstract class Factory {
    abstract public Product factoryMethod();
    public void doSomething(){
        // 
    }
}

public class ConcreateFactory extend Factory {
    public Product factoryMethod() {
        return new ConcreateProduct();
    }
}    

public class ConcreateFactory1 extends Factory {
    public Product factoryMethod() {
        return new ConcreateProduct1();
    }
}

抽象工厂 Abstract Factory:提供一个接口,用于创建相关的对象家族

抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory中的createProductA()和createProductB()方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。

至于创建对象的家族这一概念是在Client体现,Client要通过AbstractFactory同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client需要同时创建出这两个对象。从高层次来看,抽象工厂使用了组合,即Client组合了AbstractFactory,而工厂方法模式使用了继承。


public class AbstractProductA {
}

public class AbstractProductB {
}

public class ProductA1 extends AbstractProductA {
}

public class ProductA2 extends AbstractProductA {
}
    
public class ProductB1 extends AbstractProductB {
}

public class ProductB2 extends AbstractProductB {
}

public abstract class AbstractFactory {
    abstract AbstractProductA createProductA();
    abstract AbstractProductB createProductB();
}

public class ConcreteFactory1 extends AbstractFactory {
    AbstractProductA createProductA() {
        return new ProductA();
    }
    AbstractProductB createProductB() {
        return new ProductB();
    }
}

public class ConcreteFactory2 extends AbstractFactory {
    AbstractProductA createProductA() {
        return new ProductA();
    }
    AbstractProductB createProductB() {
        return new ProductB();
    }
}

public class Client {
    public static void main(String[] args) {
        AbstractFactory af = new ConcreateFactory1();
        AbstractProductA a = af.createProductA();
        AbstractProductB b = af.createProductB();
    }
}

生成器模式 Builder:封装一个对象的构造过程,并允许按步骤构造

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

  • Product 产品类:通常是实现了模板方法模式,也就是有模板方法和基本方法
  • Builder 抽象建造者:规范产品的组件,一般是由子类实现。
  • ConcreteBuilder 具体建造者:实现抽象类定义的所有方法,并且返回一个组建好的对象
  • Director 导演类:负责安排已有模块的顺序,就会告诉Builder开始建造
  • 使用场景:
    • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式
    • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式
    • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适
  • 与抽象工厂的区别:在生产者模式里,有个指导者,由指导者来管理生产者,用户是与指导者联系的,指导者联系生产者最后得到产品。即生产者模式可以强制实行一种分步骤进行的建造过程。
  • 与工厂模式的区别:建造者模式最主要的功能是基本方法的调用顺序安排,这些基本方法已经实现了,顺序不同,产生的对象也不同。工厂方法则重点是创建,创建零件是他的主要职责,组装顺序则不是他关心的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下是简易的StringBuilder实现,参考了JDK8源码


public class AbstractStringBuilder {

    protected char[] value;
    protected int count;

    public AbstractStringBuilder(int capacity) {
        count = 0;
        value = new char[capacity];
    }

    public AbstractStringBuilder append(char c) {
        ensureCapacityInternal(count + 1);
        value[count++] = c;
        return this;
    }

    private void ensureCapacityInternal(int minimumCapacity) {
        if (minimumCapacity - value.length > 0) {
            expandCapacity(minimumCapacity);
        }
    }

    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0) {
            newCapacity = minimumCapacity;
        }
        if (newCapacity < 0) {
            if (minimumCapacity < 0) {
                throw new OutOfMemoryError();
            }
            newCapacity = Integer.MAX_VALUE;
        }
    }
}

public class StringBuilder extends AbstractStringBuilder {
    public StringBuilder() {
        super(16);
    }

    @Override
    public String toString() {
        return new String(value, 0, count);
    }
}

public class Client {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        final int count = 20;
        for (int i = 0; i < count; i++) {
            sb.append((char)('a'+i));
        }
        System.out.println(sb.toString());
    }
}

原型模式 Prototype:使用原型实例指定要创建对象的类型,通过复制这个原型来创建新的对象

原型模式主要用于对象的复制,它的核心就是类图中的原型类PrototypePrototype类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。

  • 重写Object类的clone方法。java中,所有类的父类都是Object类,Object类有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

  • 优点

    • 性能优良
      • 原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点
    • 逃避构造函数的约束
      • 既是优点也是缺点,直接在内存中拷贝,构造函数是不会执行的
  • 使用场景

    • 资源优化场景
      • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
    • 性能和安全要求的场景
      • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
    • 一个对象多个修改者的场景
      • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用

public class PrototypeClass implements Cloneable{ 
  // 覆写父类 Object 方法
  @Override
  public PrototypeClass clone() { 
    PrototypeClass prototypeClass = null; 
    try {
        prototypeClass = (PrototypeClass)super.clone(); 
    } catch (CloneNotSupportedException e) {
    //异常处理
  }
    return prototypeClass;
  }
}

行为型

责任链 Chain Of Responsibility:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

责任链模式结构的重要核心模块

  • Handler 抽象处理者

    • 定义一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者定义了一个抽象处理者类型的对象作为其对下家的引用。通过该引用,处理者可以连成一条链。
      • 请求的处理方法 handleMessage,唯一对外开放的方法
      • 链的编排方法 setNext,设置下一个处理者
      • 具体的请求者必须实现的两个方法:定义自己能够处理的级别 getHandlerLevel 和具体的处理任务 echo
    • 链中的节点数量需要限制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能
  • ConcreteHandler 具体处理者

抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者,在具体处理者中可以访问链中下一个对象,以便请求的转发。


public abstract class Handler {
  private Handler nextHandler;
  //每个处理者都必须对请求做出处理
  public final Response handleMessage(Request request) {
    Response response = null;
    //判断是否是自己的处理级别
    if(this.getHandlerLevel().equals(request.getRequestLevel())) { 
      response = this.echo(request);
    } else { 
        //不属于自己的处理级别
        //判断是否有下一个处理者
      if(this.nextHandler != null) {
        response = this.nextHandler.handleMessage(request);
      } else {
        //没有适当的处理者,业务自行处理
      }
    }
    
    return response;
  }
  
  // 设置下一个处理者是谁
  public void setNext(Handler _handler) { 
    this.nextHandler = _handler;
  }
  // 每个处理者都有一个处理级别
  protected abstract Level getHandlerLevel(); 
  // 每个处理者都必须实现处理任务
  protected abstract Response echo(Request request);
}

命令 Command:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能
  • 使用命令来参数化其他对象
  • 将命令放入队列中进行排队
  • 将命令的操作记录到日志中
  • 支持可撤销的操作

下面说一下关键的一些类

  1. Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令
  2. ConcreteCommane类:Command类的实现类,对抽象类中声明的方法进行实现
  3. Client类:最终的客户端调用类
  4. Invoker类:调用者,负责调用命令
  5. Receiver类:接收者,负责接收命令并且执行命令

public interface Command {
    void execute();
}

public class LightOnCommand implements Command {
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}

public class LightOffCommand implements Command {
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute(){
        light.off();
    }
}

public class Light {
    public void on() {
        System.out.println("light is on");
    }
    public void off() {
        System.out.println("light is off");
    }
}

public class Invoker {
    private Command[] onCommands;
    private Command[] offCommands;;
    private final int slotNum = 7;

    public Invoker() {
        this.onCommands = new Command[slotNum];
        this.offCommands = new Command[slotNum];
    }

    public void setOnCommand(Command command, int slot) {
        onCommands[slot] = command;
    }

    public void setOffCommand(Command command, int slot) {
        offCommands[slot] = command;
    }
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }
}

public class Client {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        Light light = new Light();
        Command lightOnCommand = new LightOnCommand(light);
        Command lightOffCommand = new LightOffCommand(light);
        invoker.setOnCommand(lightOnCommand, 0);
        invoker.setOffCommand(lightOffCommand, 0);
        invoker.onButtonWasPushed(0);
        invoker.offButtonWasPushed(0);
    }
}
解释器 Interpreter:为语言创建解释器,通常由语言的语法和语法分析来定义(少用)
  • TerminalExpression:终结符表达式,每个终结符都需要一个TerminalExpression
  • Context:上下文,包含解释器之外的一些全局信息

以下是一个规则检验器实现,具有and和or规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。

public abstract class Expression {
    public abstract boolean interpret(String str);
}

public class TerminalExpression extends Expression {
    private String literal = null;

    public TerminalExpression(String str) {
        literal = str;
    }

    public boolean interpret(String str) {
        StringTokenizer st = new StringTokenizer(str);
        while(st.hasMoreTokens) {
            String test = st.nextToken();
            if (test.equals(literal)){
                return true;
            }
        }
        return false;
    }
}

public class AndExpression extends Expression {
    private Expression expression1 = null;
    private Expression expression2 = null;

    public AndExpression(Expression expression1, Expression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    public boolean interpret(String str){
        return expression1.interpret(str) && expression2.interpret(str);
    }
}

public class OnExpression extends Expression {
    private Expression expression1 = null;
    private Expression expression2 = null;

    public OrExpression(Expression expression1, Expression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    public boolean interpret(String str) {
        return expression1.interpret(str) || expression2.interpret(str);
    }
}

public class Client {
    
    public static Expression buildInterpreterTree() {
        Expression e1 = new TerminalExpression("A");
        Expression e2 = new TerminalExpression("B");
        Expression e3 = new TerminalExpression("C");
        Expression e4 = new TerminalExpression("D");

        Expression a1 = new OrExpression(e2, e3);
        Expression a2 = new OrExpression(e1, a1);

        return new AndExpression(e4, a2);

    }
}

迭代器 Iterator:提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。(该模式已经被淘汰,Java中已经把迭代器运用到各个聚集类中了,使用java自带的迭代器已经满足我们的需求了)
  • Aggregate 是聚合类,其中createIterator()方法可以产生一个Iterator
  • Iterator 主要定义了hasNext()next()方法
  • Client 组合了Aggregate,为了迭代遍历Aggregate,也需要组合Iterator

public interface Aggregate {
    Iterator createIterator();
}

public class ConcreteAggregate implements Aggregate {
    private Integer[] items;

    public ConcreteAggregate() {
        items = new Integer[10];
        for (int i = 0; i < items.length; i++) {
            items[i] = i;
        }
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator<Integer>(items);
    }
}

public interface Iterator<Item> {
    Item next();

    boolean hasNext();
}

public class ConcreteIterator<Item> implements Iterator {
    private Item[] items;
    private int position = 0;

    public ConcreteIterator(Item[] items) {
        this.items = items;
    }

    @Override
    public Object next() {
        return items[position++];
    }

    @Override
    public boolean hasNext() {
        return position < items.length;
    }
}

public class Client {
    
    public static void main(String[] args) {
        Aggregate aggregate = new ConcreteAggregate();
        Iterator<Integer> it = aggregate.createIterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
中介者 Mediator:用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互
  • Mediator:抽象中介者,定义统一接口用于与各同事(Colleague)对象通信
  • Concrete Mediator:具体中介者,通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色
  • Colleague :同事,每个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类地行为分为两种:
    • 一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖
    • 一种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)
  • 使用场景
    • 适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构,即每个类都与其他的类有直接的联系

一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,不利于类的复用,也不稳定。如果引入中介者模式,那么同事类之间的关系将变为星型结构,任何一个类的变动,只会影响本身以及中介者,这样就减小系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。

public abstract class Mediator {
  //定义同事类
  protected ConcreteColleague1 c1;
  protected ConcreteColleague2 c2;
  //通过 getter/setter 方法把同事类注入进来
  public ConcreteColleague1 getC1() {
    return c1;
  }
  
  public void setC1(ConcreteColleague1 c1) { 
    this.c1 = c1;
  }
  
  public ConcreteColleague2 getC2() {
    return c2;
  }
  
  public void setC2(ConcreteColleague2 c2)
  { 
    this.c2 = c2;
  }
  
  //中介者模式的业务逻辑
  public abstract void doSomething1();
  public abstract void doSomething2();
}
  • 使用同事类注入而不使用抽象注入的原因是因为抽象类中不具有每个同事类必须要完成的方法。即每个同事类中的方法各不相同
  • 为什么同事类要使用构造函数注入中介者,而中介者使用 getter/setter 方式注入同事类呢?
    • 因为同事类必须有中介者,而中介者却可以只有部分同事类
备忘录 Memento:在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。
  • 使用场景
    • 需要保存和恢复数据的相关状态场景
    • 提供一个可回滚的操作
    • 需要监控的副本场景中
    • 数据库连接的事务管理就是用的备忘录模式
  • 注意
    • 备忘录的生命周期
    • 备忘录的性能
      • 不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环)
  1. Originator:原始对象
  2. Caretaker:负责保存好备忘录
  3. Memento:备忘录,存储原始对象的状态。备忘录实际上有两个接口,一个是提供给Caretaker的窄接口:它只能将备忘录传递给其他对象;一个是提供给Originator的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许Originator访问本备忘录的内部状态。

以下实现了一个简单计算器程序,可以输入两个值,然后计算两个值的和,备忘录模式允许将这两个值存储起来,然后在某个时刻用储存的状态进行恢复。

public interface Calculator {

    PreviousCalculationToCareTaker backupLastCalculation();

    void restorePreviousCalculatoin(PreviousCalculationToCareTaker memento);

    int getCalculationResult();

    void setFirstNumber(int firstNumber);

    void setSecondNumber(int secondNumber);
}

public class CalcuatorImp implemnets Calculator {

    private int firstNumber;
    private int secondNumber;

    @Override
    public PreviousCalculationToCareTaker backupLastCalculation() {
        return new PreviousCalculationImp(firstNumber, secondNumber);
    }

    @Override
    public void restorePreviousCalculation(PreviousCalculationToCareTaker memento) {
        this.firstNumber = ((PreviousCalculationToOriginator)memento).getFirstNumber();
        this.secondNumber = ((PreviousCalculationToOriginator)memento).getSecondNumber();
    }
}

public interface PreviousCalculationToCareTaker {
}

public class PreviousCalculationImp implements PreviousCalculationToCareTaker, PreviousCalculationToOriginator {
    private int firstNumber;
    private int secondNumber;

    public PreviousCalculationImp(int firstNumber, int secondNumber) {
        this.firstNumber = firstNumber;
        this.secondNumber = secondNumber;
    }

    @Override
    public int getFirstNumber() {
        return firstNumber;
    }

    @Override
    public int getSecondNumber() {
        return secondNumber;
    }
}

public class Client {
    public static void main() {
       Calculator calculator = new Calculator();
       calculator.setFirstNumber(10);
       calculator.setSecondNumber(100);

       System.out.println(calculator.getCalculationResult());

       PreviousCalculationToCareTaker memento = calculator.backupLastCalculation();
       calculator.setFirstNumber(17);
       calculator.setSecondNumber(-290);

       System.out.println(calculator.getCalculationResult());
       calculator.restorePreviousCalculation(memento);
       System.out.println(calculator.getCalculationResult());
    }
}
观察者 Observer:定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。
  • 使用场景
    • 关联行为场景。需要注意的是,关联行为是可拆分的,不是组合关系
    • 事件多级触发场景
    • 跨系统的消息交换场景,如消息队列的处理机制
  • 注意
    • 广播链的问题
      • 在一个观察者模式中最多出现一个对象,既是观察者,也是被观察者,也就是说消息最多转发一次,传递两次
    • 异步处理问题
      • 观察者比较多,而且处理时间比较长,采用异常处理来考虑线程安全和队列的问题
  • 主题是被观察的对象,而其所有依赖者称为观察者。主题具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。观察者的注册功能需要调用主题的registerObserver()方法。

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObserver(Observer o);
}

public class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeacherData() {
        observers = new ArrayList();
    }
    public void setMeasurements() {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObserver();
    }   
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }
    @Override
    public void notifyObserver() {
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }
}

public interface Observer {
    void update(float temp, float humidity, float pressure);
}

public class StatisticsDisplay implements Observer {
    public StatisticsDisplay(Subject weacherData) {
        weacherData.registerObserver(this);
    }
    @Override
    public void update(float temp, float humidity, float pressure) {
        System.out.println("StatisticsDisplay.update: "+temp+" "+humidity+" "+pressure);
    }
}

public class CurrentConditionsDisplay implements Observer {
    public CurrentConditionsDisplay(Subject weacherData) {
        weacherData.registerObserver(this);
    }
    @Override
    public void update(float temp, float humidity, float pressure) {
        System.out.println("CurrentConditionsDisplay.update: "+temp+" "+humidity+" "+pressure);
    }
}

public class WeacherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay();
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        
        weatherData.setMeasurements(0, 0, 0);
        weatherData.setMeasurements(1, 1, 1);
    }
}
状态 state: 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
  • 使用场景

    • 行为随状态改变而改变的场景
      • 也是状态模式的根本出发点,例如权限设计,人员的状态不同,即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式
    • 条件、分支判断语句的替代者
  • 注意

    • 状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个
  • 糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。


public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}
public class GumballMachine {
    private State soldOutState;
    private State noQuarterState;
    private State hasQuarterState;
    private State soldState;
    private State state;
    private int count = 0;
    
    public GumballMachine(int numberGumballs) {
        count = numberGumballs;
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
        if (numberGumballs > 0) {
            state = noQuarterState;
        } else {
            state = soldQuarterState;
        }
    }
    public void insertQuarter() {
        state.insertQuarter();
    }
    public void ejectQuarter() {
        state.ejectQuarter();
    }
    public void turnCrank() {
        state.turnCrank();
    }
    public void setState(State state) {
        this.state = state;
    }
    public void releaseBall() {
        System.out.println("A gumball comes rolling out the slot ...");
        if (count != 0) {
            count -= 1;
        }
    }
    public State getSoldOutState() {
        return soldOutState;
    }
    public State getNoQuarqterState() {
        return noQuarterState;
    }
    public State getHasQuarterState() {
        return hasQuarterState;
    }
    public State getSoldState() {
        return soldState;
    }
    public int getCount() {
        return count;
    }
}

public class HasQuarterState implements State {
    private GumballMachine gumballMachine;
    
    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("You can't insert another quarter");
    }
    @Override
    public void ejectQuarter() {
        System.out.println("Quarter returned");
        gumballsMachine.setState(gumballMachine.getNoQuarterState());
    }
    @Override
    public void turnCrank() {
        System.out.println("You turned ...");
        gumballMachine.setState(gumballMachine.getSoldState());
    }
    @Override
    public void dispense() {
        System.out.println("No gumball dispensed");
    }
}

public class NoQuarterState implements State {
 // ...
}

public class SoldOutState implements State {
 // ...
}

public class SoldState implements State {
 // ...
}

public class Client {
    GumballMachine gumballMachine = new GumballMachine(5);
    gumballMachine.insertQuarter();
    gumballMachine.turnCrank();
    gumballMachine.insertQuarter();
}
策略 Strategy: 定义一系列算法,封装每个算法,并使它们可以互换。策略模式可以让算法独立于使用它的客户端。
  • Strategy 抽象策略角色
    • 接口定义了一个算法族,它们都实现了behavior方法
  • Context 封装角色
    • 是使用到该算法族的类,其中的doSomething方法会调用behavior,setStrategy(Strategy)方法可以动态地改变strategy对象,也就是说能动态地改变Context所使用的算法。
  • Concrete Strategy 具体策略角色(多个)
    • 实现抽象策略中的操作,该类含有具体的算法
  • 使用场景
    • 多个类只有在算法行为上稍有不同的场景
    • 算法需要自有切换的场景
    • 需要屏蔽算法规则的场景

状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变Context所组合的State对象,而策略模式是通过Context本身的决策来改变组合的Strategy对象。所谓的状态转移,是指Context在运行过程中由于一些条件发生改变而使得State对象发生改变,注意必须要是在运行过程中。

状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么Context对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换Context使用的算法。

设计一个鸭子,它可以动态地改变叫声,这里的算法族是鸭子的叫声行为。

public interface QuackBehavior {
    void quack();
}

public class Quack implements QuackBahavior {
    @Override
    public void quack() {
        System.out.println("quack!");
    }
}

public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("squeak!");
    }
}

public class Duck {
    private QuackBehavior quackBehavior;
    public void performQuack() {
        if (quackBehavior != null) {
            quackBehavior.quack();
        }
    }
    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}

public class Client {
    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.setQuackBehavior(new Squeak());
        duck.performQuack();
        duck.setQuackBehavior(new Quack());
        duck.performQuack();
    }
}
模板方法 Template Method :**定义算法框架,并将一些步骤的实现延迟到子类。通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。 **
  • 方法分为两类

    • 基本方法:也叫做基本操作,是由子类实现的方法,并且在模板方法被调用
    • 模板方法:可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑
      • 为了防止恶意操作,一般模板方法都加上final关键字,不允许被覆写
  • 使用场景

    • 多个子类有共有的方法,并且逻辑基本相同时
    • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现
    • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为
  • 冲咖啡和冲茶都有类似的流程,但是因为某些步骤会有点不一样,要求复用那些相同步骤的代码。

public abstract class CaffeineBeverage {
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    abstract void brew();
    abstract void addCondiments();
    void boilWater() {
        System.out.println("boilWater");
    }
    void pourInCup() {
        System.out.println("pourInCup");
    }
}
访问者 visitor : 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作
  1. Visitor:访问者,抽象类或接口,声明访问者可以访问哪些元素,为每一个ConcreteElement声明一个visit操作,该方法参数定义哪些对象是可以被访问的
  2. ConcreteVisitor:具体访问者,它影响访问者访问到一个类后该怎么干,要做什么事情
  3. Element:抽象元素,接口或抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义
  4. Concrete Element:具体元素,实现 accept 方法,通常是 visitor.visit(this),基本上都形成了一种模式
  5. Object Structure:对象结构,可以是组合结构,或者是一个集合,元素产生者,一般容纳在多个不同类、不同接口的容器,如 List、Set、Map 等,在项目中,一般很少抽象出这个角色
  • 使用场景
    • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就是说,用迭代器模式已经不能胜任的情景
    • 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作污染这些对象的类
public interface Element {
    void accept(Visitor visitor);
}
class CustomerGroup {
    private List<Customer> customers = new ArrayList();
    void accept(Visitor visitor) {
        for (Customer customer : customers) {
            customer.accept(visitor);
        }
    }
    void addCustomer(Customer customer) {
        customers.add(customer);
    }
}
public class Customer implements Element {
    private String name;
    private List<Order> orders = new ArrayList();
    Customer(String name) {
        this.name = name;
    }
    String getName() {
        return name;
    }
    void addOrder(Order order) {
        orders.add(order);
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
        for (Order order : orders) {
            order.accept(visitor);
        }
    }
}
public class Order implements Element {
    private String name;
    private List<Item> items = new ArrayList();
    Order(String name) {
        this.name = name;
    }
    Order(String name, String itemName) {
        this.name = name;
        this.addItem(new Item(itemName));
    }
    String getName() {
        return name;
    }
    void addItem(Item item) {
        items.add(item);
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
        for (Item item : items) {
            item.accept(visitor);
        }
    }
}
public class Item implements Element {
    private String name;
    Item(String name) {
        this.name = name;
    }
    String getName() {
        return name;
    }
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
public interface Visitor {
    void visit(Customer customer);
    void visit(Order order);
    void visit(Item item);
}
public class GeneralReport implements Visitor {
    private int customersNo;
    private int ordersNo;
    private int itemsNo;
    public void visit(Customer customer) {
        System.out.println(customer.getName());
        customersNo++;
    }
    public void visit(Order order) {
        System.out.println(order.getName());
        ordersNo++;
    }
    public void visit(Item item) {
        System.out.println(item.getName());
        itemsNo++;
    }
    public void displayResults() {
        System.out.println(customersNo);
        System.out.println(itemsNo);
        System.out.println(ordersNo);
    }
}

public class Client {
    public static void main(String[] args) {
        Customer customer1 = new Customer("customer1");
        customer1.addOrder(new Order("order1", "item1"));
        customer1.addOrder(new Order("order2", "item1"));
        customer1.addOrder(new Order("order3", "item1"));
        
        Order order = new Order("order_a");
        order.addItem(new Item("item_a1"));
        order.addItem(new Item("item_a2"));
        order.addItem(new Item("item_a3"));
        
        Customer customer2 = new Customer("customer2");
        customer2.addOrder(order);
        
        CustomerGroup customers = new CustomerGroup();
        customers.addCustomer(customer1);
        customers.addCustomer(customer2);
    
        GeneralReport visitor = new GeneralReport();
        customers.accept(visitor);
        visitor.displayResults();
    }
}

结构型

适配器 Adapter :将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作
  • 类适配器

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

    • 类适配器是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系,这是两者的根本区别(实际项目中对象适配器使用到的场景相对比较多)
  • 使用场景

    • 有动机修改一个已经投产中的接口时,适配器模式可能是最合适的模式。但是请注意,详细设计阶段不要考虑使用适配器模式,使用主要场景为扩展应用中

鸭子和火鸡拥有两种不同的叫声,鸭子的叫声调用quack方法,而火鸡调用gobble方法。要求将火鸡的gobble方法适配成鸭子的quack方法,从而让火鸡冒充鸭子

public interface Duck {
    void quack();
}
public interface Turkey {
    void gobble();
}
public class WildTurkey implements Turkey {
    @Override
    public void gobble() {
        System.out.println("gobble!");
    }
}
public class TurkeyAdapter implements Duck {
    Turkey turkey;
    
    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }
    @Override
    public void quack() {
        turkey.gobble();
    }
}
public class Client {
    public static void main(String[] args) {
        Turkey turkey = new WildTurkey();
        Duck duck=  new TurkeyAdapter(turkey);
        duck.quack();
    }
}
桥接 Bridge:将抽象与实现分离开来,使他们可以独立变化
  • 使用场景
    • 不希望或不适用使用继承的场景
    • 接口或抽象类不稳定的场景
    • 重用性要求较高的场景
  • 注意
    • 发现类的继承有N层时,可以考虑使用桥梁模式,桥梁模式主要考虑如何拆分抽象和实现
  1. Abstraction : 定义抽象类的接口
  2. Implementor : 定义实现类的接口

RemoteControl表示遥控器,指代Abstraction。TV表示电视,指代Implementor。桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。

public abstract class TV {
    public abstract void on();
    public abstract void off();
    public abstract void tuneChannel();
}
public class Sony extends TV {
    @Override
    public void on() {
       System.out.println("Sony.on()");
    }
    @Override
    public void off() {
        System.out.println("Sony.off()");
    }
    @Override   
    public void tuneChannel() {
        System.out.println("Sony.tuneChannel()");
    }
}
组合 Composite :将对象组合成树形结构来表示"整体/部分"层次关系,允许用户以相同的方式处理单独对象和组合对象。
  • 组件类是组合类和叶子类的父类,可以把组合类看成是树的中间节点。组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。
  • Component 抽象构件角色
    • 定义参加组合对象的共有方法属性,可以定义一些默认的行为或属性
  • Leaf 叶子构件
    • 叶子对象,其下再也没有其它的分支,也就是遍历的最小单位
  • Composite 树枝构件
    • 树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构
public class Composite extends Component { 
  //构件容器
  private ArrayList<Component> componentArrayList = new ArrayList<Component>();
  //增加一个叶子构件或树枝构件
  public void add(Component component) { 
    this.componentArrayList.add(component);
  }
  //删除一个叶子构件或树枝构件
  public void remove(Component component) { 
    this.componentArrayList.remove(component);
  }
  //获得分支下的所有叶子构件和树枝构件 public
  ArrayList<Component> getChildren() {
    return this.componentArrayList;
  }
}

  • 使用场景
    • 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理
    • 从一个整体中能够独立出部分模块或功能的场景
    • 注意:只要是树形结构,就考虑使用组合模式
装饰 Decorator : 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活
  • Component 抽象构件

    • 是一个接口或者是抽象类,就是定义最核心的对象
  • Concrete Component 具体构件

    • Component 接口或抽象类的实现,需要装饰的就是它
  • Decorator 装饰角色

    • 一般是一个抽象类,做什么用呢?实现接口或者抽象方法,它里面可不一定有抽象的方法,在它的属性里必然有一个private变量指向Component抽象构件
  • Concrete Decorator 具体装饰角色

    • 两个具体的装饰类,要把最核心的、最原始的、最基本的东西装饰成其他东西
  • 使用场景

    • 需要扩展一个类的功能,或给一个类增加附加功能
    • 需要动态给一个对象增加功能,这些功能可以再动态地撤销
    • 需要为一批兄弟类进行改装或加装功能,当然是首选装饰模式
  • 装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component)
    ,具体组件的方法实现不需要依赖于其他对象,而装饰者组合了一个组件,这样它可以装饰其他装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最底层,因为只有具体组件的方法实现不需要依赖于其他对象。

设计原则:类应该对扩展开放,对修改关闭。也就是添加新功能时不需要修改代码。饮料可以动态添加新的配料,而不需要去修改饮料的代码。

门面 Facade :提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。
  • 使用场景
    • 为一个复杂的模块或子系统提供一个供外界访问的接口
    • 子系统相对独立,外界对子系统的访问只要黑箱操作即可
    • 预防低水平人员带来的风险扩散
  • 注意
    • 一个子系统可以有多个门面
    • 门面不参与子系统内的业务逻辑

设计原则:只和你的密友谈话,也就是说客户对象所需要交互的对象应当尽可能的少。

享元 Flyweight :利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。
  • 对象的信息分为两个部分:内部状态外部状态
    • 内部状态
      • 对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变
    • 外部状态
      • 对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态
  • 使用场景
    • 系统中存在大量的相似对象
    • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
    • 需要缓冲池的场景
  • 注意
    • 享元模式是线程不安全的,只有依靠经验,在需要的地方考虑一下线程安全,在大部分场景下不用考虑。对象池中的享元对象尽量多,多到足够满足为止
    • 性能安全:外部状态最好以java的基本类型作为标志,如 String、int,可以提高效率
  1. Flyweight : 享元对象,该角色需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同时修改了外部状态,这是绝对不允许的
  2. IntrinsicState : 内部状态,享元对象共享内部状态
  3. ExtrinsicState : 外部状态,每个享元对象的外部状态不同
public class FlyweightFactory {
    //定义一个池容器
    private static HashMap<String,Flyweight> pool = new HashMap<String,Flyweight>();
    //享元工厂
    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;
    }
}
代理 Proxy : 为其他对象提供一种代理以控制对这个对象的访问
  • Subject 抽象主题角色
    • 抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求
  • RealSubject 具体主题角色
    • 也叫被委托角色被代理角色,是业务逻辑的具体执行者
  • Proxy 代理主题角色
    • 也叫委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理善后处理工作
  • 普通代理
    • 就是我们要知道代理的存在,然后才能访问
    • 调用者只知道代理,而不用知道真实角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合
  • 强制代理
    • 则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的
    • 这个概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本不需要产生一个代理出来,代理的管理已经由真实角色自己完成
  • 动态代理
    • 根据被代理的接口生成所有的方法,也就是说,给定一个接口,动态代理会宣称,我已经实现该接口下的所有方法了
    • 两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块(也就是Client)进行耦合,完成逻辑的封装任务
    • 调用过程
      • 动态代理的意图:横切面编程,在不改变我们已有代码结构的情况下,增强或控制对象的行为
      • 首要条件:被代理的类必须要实现一个接口

设计原则

  • 单一责任原则

修改一个类的原因应该只有一个

换句话说就是让一个类只负责一件事,当这个类需要做过多事情时,就需要分解这个类。如果一个类承担的指责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱这个类完成其他职责的能力。

  • 开放封闭原则

类应该对扩展开放,对修改关闭

扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。符合开闭原则最典型的设计模式就是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。

  • 里氏替换原则

子类对象必须能够替换掉所有父类对象

继承是一种IS-A关系,子类需要能够当成父类来使用,并且需要比父类更特殊。如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。

  • 接口分离原则

不应该强迫客户依赖于它们不用的方法

因此使用多个专门的接口比使用单一的总接口要好

  • 依赖倒置原则

高层模块不应该依赖于底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。

依赖于抽象意味着:

  1. 任何变量都不应该有一个指向具体类的指针或者引用
  2. 任何类都不应该从具体类派生
  3. 任何方法都不应该覆写它的任何基类中的已经实现的方法
  • 迪米特法则

LKP,最少知识原则,一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

  • 合成复用原则

尽量使用对象组合,而不是通过继承来达到复用的目的

  • 共同封闭原则

一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。

  • 稳定抽象原则

最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟他的稳定性成正比

  • 稳定依赖原则

包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性
的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合

  • 强制代理
    • 则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的
    • 这个概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本不需要产生一个代理出来,代理的管理已经由真实角色自己完成
  • 动态代理
    • 根据被代理的接口生成所有的方法,也就是说,给定一个接口,动态代理会宣称,我已经实现该接口下的所有方法了
    • 两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块(也就是Client)进行耦合,完成逻辑的封装任务
    • 调用过程
      • 动态代理的意图:横切面编程,在不改变我们已有代码结构的情况下,增强或控制对象的行为
      • 首要条件:被代理的类必须要实现一个接口

设计原则

  • 单一责任原则

修改一个类的原因应该只有一个

换句话说就是让一个类只负责一件事,当这个类需要做过多事情时,就需要分解这个类。如果一个类承担的指责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱这个类完成其他职责的能力。

  • 开放封闭原则

类应该对扩展开放,对修改关闭

扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。符合开闭原则最典型的设计模式就是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。

  • 里氏替换原则

子类对象必须能够替换掉所有父类对象

继承是一种IS-A关系,子类需要能够当成父类来使用,并且需要比父类更特殊。如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。

  • 接口分离原则

不应该强迫客户依赖于它们不用的方法

因此使用多个专门的接口比使用单一的总接口要好

  • 依赖倒置原则

高层模块不应该依赖于底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。

依赖于抽象意味着:

  1. 任何变量都不应该有一个指向具体类的指针或者引用
  2. 任何类都不应该从具体类派生
  3. 任何方法都不应该覆写它的任何基类中的已经实现的方法
  • 迪米特法则

LKP,最少知识原则,一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

  • 合成复用原则

尽量使用对象组合,而不是通过继承来达到复用的目的

  • 共同封闭原则

一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。

  • 稳定抽象原则

最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟他的稳定性成正比

  • 稳定依赖原则

包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1425354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt实现窗口吸附屏幕边缘 自动收缩

先看效果&#xff1a; N年前的QQ就可以吸附到屏幕边缘&#xff0c;聊天时候非常方便&#xff0c;不用点击状态栏图标即可呼出QQ界面 自己尝试做了一个糙版的屏幕吸附效果。 关键代码&#xff1a; void Widget::mouseMoveEvent(QMouseEvent *e) {int dx e->globalX() - l…

C语言基础:写一个函数,输入一行字符,将此字符串最长的单词输出

方法一&#xff1a; #include<string.h> int find_longest(char line[])//把数组传过来 {int is_alphabetic(char word);int i 0;int length 0;//统计每个字符串的长度int max 0;//比max长就把值赋值给maxint place 0;//最长单词的起始位置int point;//每个字符串第…

暴搜,回溯,剪枝

力扣77.组合 class Solution {List<List<Integer>>retnew ArrayList<>();List<Integer>pathnew ArrayList<>();int n; int k;public List<List<Integer>> combine(int _n, int _k) {n_n;k_k;dfs(1);return ret;}public void dfs(int…

2024斋月大促跨境卖家准备指南

市场覆盖西欧、中东、东南亚、北非地区的跨境电商卖家注意了&#xff0c;2024年的斋月即将开启&#xff0c;较往年日期&#xff0c;今年提前了10天左右&#xff0c;斋月的第一天预测在3月11日星期一到来。 根据Google搜索数据可知&#xff0c;目前已经进入高频“斋月”搜索期&…

小米商城服务治理之客户端熔断器(Google SRE客户端熔断器)

目录 前言 一、什么是Google SRE熔断器 二、Google SRE 熔断器的工作流程&#xff1a; 三、客户端熔断器 (google SRE 熔断器) golang GRPC 实现 四、客户端熔断器 (google SRE 熔断器) golang GRPC单元测试 大家可以关注个人博客&#xff1a;xingxing – Web Developer …

K8S网络

一、介绍 k8s不提供网络通信&#xff0c;提供了CNI接口(Container Network Interface&#xff0c;容器网络接口)&#xff0c;由CNI插件实现完成。 1.1 Pod通信 1.1.1 同一节点Pod通信 Pod通过虚拟Ethernet接口对&#xff08;Veth Pair&#xff09;与外部通信&#xff0c;Veth…

银河麒麟v10服务器版,specvirt测试

1 两台服务器&#xff0c;一台为SUT&#xff0c;一台为Phyclient。 1.1 两台服务器均编译安装gcc和qemu 按银河麒麟v10服务器arm版&#xff0c;qemugcc&#xff0c;跨架构安装虚拟机中步骤&#xff0c;编译安装gcc-9.3.0和qemu-7.0.0。 2 SUT服务器操作 2.1 mount数据盘到/…

如何发布自己的npm包:

1.创建一个打包组件或者库&#xff1a; 安装weback&#xff1a; 打开项目&#xff1a; 创建webpack.config.js,创建src目录 打包好了后发现两个js文件都被压缩了&#xff0c;我们想开发使用未压缩&#xff0c;生产使用压缩文件。 erserPlugin&#xff1a;&#xff08;推荐使用…

搭建 idea 插件仓库私服

正常情况下&#xff0c;我们开发的 idea 插件会发布到 idea 官方商城中&#xff0c;这样用户就可以在 idea 的 Marketplace 中搜索安装。 但是在企业内部&#xff0c;有可能我们开发了很多内部插件&#xff0c;而不能发布到公共市场中&#xff0c;这种情况下我们就需要搭建一个…

css多行文本擦拭效果

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>多行文本擦拭效果</title><style>* …

black--一键格式化Python代码

black black是一个Python代码格式化程序&#xff0c;使用它可以免于在调整代码格式上花费时间。black被许多大大小小的项目成功使用&#xff0c;包括pytest, tox, Pyramid, Django等。 格式化效果&#xff1a; 可以在线查看格式化效果&#xff1a;https://black.vercel.app/…

ERP系统助力车间生产:班组、设备、工序一网打尽!实现生产全流程可视化!

​随着企业生产规模的扩大和业务复杂性的增加&#xff0c;车间管理在企业运营中的地位日益突出。ERP系统作为企业资源管理的核心平台&#xff0c;为车间管理提供了全面的解决方案。通过合理配置和使用ERP系统的功能模块&#xff0c;企业可以优化生产流程、提高生产效率、确保产…

【SparkML系列3】特征提取器TF-IDF、Word2Vec和CountVectorizer

本节介绍了用于处理特征的算法&#xff0c;大致可以分为以下几组&#xff1a; 提取&#xff08;Extraction&#xff09;&#xff1a;从“原始”数据中提取特征。转换&#xff08;Transformation&#xff09;&#xff1a;缩放、转换或修改特征。选择&#xff08;Selection&…

一文看懂动态住宅代理IP,附常见使用问题解答

动态住宅代理IP在保护在线隐私和个人数据安全方面发挥着重要作用。通过隐藏用户的真实IP地址和地理位置&#xff0c;它为网络用户提供了一个更安全、更私密的网络环境。这对于希望保护自己免受网络监控和个人信息泄露的用户来说&#xff0c;是一项不可或缺的网络工具。 一、动态…

RT-Thread:STM32的PB3,PB4 复用IO配置为GPIO

说明&#xff1a;在使用 STM32F103CBT6 配置了 PB3 为IO&#xff0c;测试时发现读取这个IO的电平时钟是0&#xff0c;即便单管脚上的电平是1&#xff0c;读取的数据任然是0,查规格书后发现PB3,PB4是JTAG复用口&#xff0c;要当普通IO用需要配置。 配置工具&#xff1a;STM32Cu…

React中封装大屏自适应(拉伸)仿照 vue2-scale-box

0、前言 仿照 vue2-scale-box 1、调用示例 <ScreenAutoBox width{1920} height{1080} flat{true}>{/* xxx代码 */}</ScreenAutoBox> 2、组件代码 import { CSSProperties, ReactNode, RefObject, useEffect, useRef, useState } from react//数据大屏自适应函数…

36万的售价,蔚来理想卖得,小米卖不得?

文 | AUTO芯球 作者 | 雷歌 Are you OK&#xff1f;雷军被网友们叫“小雷”&#xff01; 被网友一猜再猜的小米SU7的价格&#xff0c;因为一份保险上牌价格单的曝光被网友吵得热热闹闹&#xff0c;曝出的小米汽车顶配上牌保险价格为36.14万。 20万以下&#xff0c;人们愿称…

Java强训day13(选择题编程题)

选择题 编程题 题目1 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String s sc.nextLine();char[] c s.toCharArray();int i 0;int t 0;while (i < c.length) {if (c[i] ! \") {…

【脑电信号处理与特征提取】P7-贾会宾:基于EEG/MEG信号的大尺度脑功能网络分析

基于EEG/MEG信号的大尺度脑功能网络分析 Q: 什么是基于EEG/MEG信号的大尺度脑功能网络分析&#xff1f; A: 基于脑电图&#xff08;EEG&#xff09;或脑磁图&#xff08;MEG&#xff09;信号的大尺度脑功能网络分析是一种研究大脑活动的方法&#xff0c;旨在探索脑区之间的功能…

qt内存自动释放的两种情况

qt内存管理机制 QObject的parent 我们时常能看到QWidget或者其他的控件的构造函数中有一项参数parent&#xff0c;默认值都为NULL&#xff0c;例如&#xff1a; QLineEdit(const QString &contents, QWidget *parent nullptr); QWidget(QWidget *parent nullptr, Qt::…