软件设计模式

创建型模式概述

创建型设计模式包括:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。

创建型的关注点是怎样创建对象,特点是将对象的创建与使用分离,优点是降低系统的耦合度,使用者不需要关心对象的创建细节,对象的创建由相关工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出一样,因为它们由专门的厂商生产。

单例模式

定义与特点

单例设计模式:一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式。

注:在有些系统中,为了节省内存资源,保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式

结构与实现

软件设计模式

饿汉

在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载(真正用到的时候再创建实例)。



 
public class Singleton {
  private static final Singleton instance = new Singleton();
  private Singleton() {}
  public static Singleton getInstance() {
    return instance;
  }
}
懒汉


 
public class Singleton { 
  private static Singleton instance;
  private Singleton() {}
  public static synchronized Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}
双重检测


public class Singleton { 
  private static Singleton instance;
  private Singleton() {}
  public static Singleton getInstance() {
    if (instance == null) {
      synchronized(Singleton.class) { // 此处为类级别的锁
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}
静态内部类


public class Singleton { 
  private AtomicLong id = new AtomicLong(0);
  private Singleton() {}
 
  private static class SingletonHolder{
    private static final Singleton instance = new Singleton();
  }
  
  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}
应用场景

要求生成唯一序列号的环境;

在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;

创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源。

应用实例


public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 获取单例对象
    @Nullable
    public Object getSingleton(String beanName) {
        return singletonObjects.get(beanName);
    }
 
    // 注册单例对象
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
        }
    }
 
    // 获取单例对象工厂
    protected ObjectFactory<?> getSingletonFactory(String beanName) {
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory == null) {
                    singletonFactory = new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            throw new IllegalStateException("Cannot call getObject() on a singleton FactoryBean " +
                                    "instance that is still in creation (i.e. it is not fully initialized yet)");
                        }
                    };
                    this.singletonFactories.put(beanName, singletonFactory);
                }
            }
        }
        return singletonFactory;
    }
 
    // 判断是否正在创建单例对象
    protected boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.earlySingletonObjects.containsKey(beanName);
    }
 
    // 单例对象的提前暴露处理
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
            }
        }
    }
 
    // 销毁单例对象
    protected void destroySingleton(String beanName) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.remove(beanName);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
        }
    }
}

工厂方法模式

定义与特点

此模式的核心精神是封装类中变化的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。

结构与实现

抽象工厂:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。

具体工厂:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。

抽象产品:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。

具体产品:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

软件设计模式

现需要实现一个能够进行计算的计算器功能。

未使用到任何设计模式:



public class Operation {
    public static double getResult(double num1,double num2,String operator){
        double result = 0;
        switch (operator){
            case "+":
                result = num1 + num2;
                break;
            case "-":
                result = num1 - num2;
                break;
            case "*":
                result = num1 * num2;
                break;
            default:
                break;
        }
        return result;
    }
 
    public static void main(String[] args) {
        System.out.println("请输入第一个数字");
        Scanner sc = new Scanner(System.in);
        double num1 = sc.nextDouble();
        System.out.println("请输入第二个数字");
        double num2 = sc.nextDouble();
        double result = getResult(num1,num2,"+");
        System.out.println(result);
    }
}

这种情况下,如果需要新增一个平方根运算,就要把其他几种运算方法类一起给到开发人员修改,本来加一个功能,却可能使原有运行良好的功能代码产生变化,有很大风险。因此应该把加减乘除等运算分离,使修改其中一个不影响另外几个。

使用简单工厂模式:

软件设计模式



public class OperationFactory {
    public static Operation cerateOperate(String operate){
        Operation oper = null;
        switch (operate){
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
            default:
                break;
        }
        return oper;
    }
}
abstract class Operation {
    double num1;
    double num2;
    abstract double getResult();
}
 
class OperationSub extends Operation{
    @Override
    double getResult() {
        return 0;
    }
}
 
class OperationAdd extends Operation{
    @Override
    double getResult() {
        return super.num1 + super.num2;
    }
}
 
class Application {
    public static void main(String[] args) {
        Operation oper = OperationFactory.cerateOperate("+");
        oper.num1 = 1;
        oper.num2 = 2;
        double result = oper.getResult();
    }
}

上述实现方式的弊端就是:

工厂类负责了所有产品的创建过程,如果产品的种类过多或者产品的创建过程比较复杂,工厂类的代码会变得非常臃肿和难以维护。

违背了开放封闭原则:当需要添加新的产品类时,需要修改工厂类的代码,从而违背了开放封闭原则。

使用工厂方法模式:

软件设计模式



public interface IFactory {
    Operation createOperation();
}
 
public class AddFactory implements IFactory{
    @Override
    public Operation createOperation() {
        return new OperationAdd();
    }
}
 
public static void main(String[] args) {
        IFactory factory = new AddFactory();
        Operation operation = factory.createOperation();
        operation.num1 = 1;
        operation.num2 = 2;
        double result = operation.getResult();
    }
优缺点

优点:

符合开放封闭原则:当需要添加新的产品时,只需要增加相应的具体产品类和对应的具体工厂类,不需要修改现有的代码,从而实现了代码的开放封闭。

符合单一职责原则:将对象的创建过程与客户端代码分离开来,使得客户端代码只需要关心产品的接口,而不需要关心具体的实现,从而提高了代码的可维护性和可扩展性。

降低了耦合度:客户端代码只依赖于抽象工厂类和抽象产品类,而不依赖于具体的实现类,从而降低了系统的耦合度,提高了系统的稳定性和可靠性。

缺点:

增加了系统的复杂度:由于需要定义多个具体工厂类和具体产品类,增加了系统的复杂度和代码量。

需要额外的开发工作:每个具体产品类都需要一个对应的具体工厂类来创建它的实例,这需要额外的开发工作,增加了系统的开发成本。

可能会导致类的数量增加:由于需要定义多个具体产品类和具体工厂类,可能会导致类的数量增加,从而影响系统的性能和可维护性。

与简单工厂的区别:

简单工厂模式适用于对象类型比较少,且不需要频繁扩展和修改的场景,而工厂方法模式适用于需要频繁扩展和修改对象类型。

应用场景

需要对产品进行扩展时。通过增加新的工厂子类,就可以实现对新产品的支持,而不需要修改现有的代码。

需要将对象的创建和使用解耦时。工厂方法将对象的创建过程封装在工厂类中,客户端只需要通过工厂接口获取产品实例即可,从而实现了对象的创建和使用的解耦。

应用实例

Dubbo注册中心使用了工厂方法模式来创建注册中心的实例,它将注册中心的实现与注册中心接口进行分离,通过一个抽象的工厂接口来定义创建注册中心的方法,具体的实现由各个注册中心厂商来提供。这样可以使得Dubbo框架更加灵活,可以支持不同类型的注册中心,也可以方便地扩展新的注册中心。



public interface RegistryFactory {
    Registry getRegistry(URL url);
}
 

定义注册中心接口Registry,其中包含了一系列注册中心的操作方法,如注册服务、订阅服务、查询服务等。



public interface Registry {
    void register(URL url);
    void unregister(URL url);
    void subscribe(URL url, NotifyListener listener);
    void unsubscribe(URL url, NotifyListener listener);
    List<URL> lookup(URL url);
}

定义具体的注册中心工厂实现类,如ZookeeperRegistryFactory、RedisRegistryFactory等,它们实现了RegistryFactory接口,并负责创建相应类型的注册中心实例。



public class ZookeeperRegistryFactory implements RegistryFactory {
 
    @Override
    public Registry getRegistry(URL url) {
        return new ZookeeperRegistry(url);
    }
 
}
 
public class RedisRegistryFactory implements RegistryFactory {
 
    @Override
    public Registry getRegistry(URL url) {
        return new RedisRegistry(url);
    }
 
}

在Dubbo中使用注册中心时,需要先获取对应的注册中心工厂实例,然后通过工厂实例创建相应类型的注册中心实例,最后调用注册中心的方法来实现相应的操作。如下所示:



public class RegistryFactoryTest {
 
    public static void main(String[] args) {
        URL url = new URL("zookeeper", "localhost", 2181);
        RegistryFactory factory = new ZookeeperRegistryFactory();
        Registry registry = factory.getRegistry(url);
        registry.register(new URL("dubbo", "localhost", 20880, "com.example.demo.DemoService"));
    }
}

使用工厂方法模式将注册中心的实现与注册中心接口进行分离,使得Dubbo框架更加灵活、可扩展。

抽象工厂模式

定义与特点

抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无需指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂类模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品

注:抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下:

可以在类的内部对产品族中的多等级产品共同管理,而不必专门引入多个新的类来进行管理

当增加一个新的产品族时不需要修改原代码,满足开闭原则

其缺点是:

当产品族中需要增加一个新的产品时,所以的工厂类都需要修改

结构与实现

软件设计模式

抽象工厂主要分为4类:

AbstractFactory:抽象工厂角色,它声明了一组用于创建一种产品的方法,每一个方法对应一种产品,如上述类图中的AbstractFactory就定义了两个方法,分别创建产品A和产品B

ConcreteFactory:具体工厂角色,它实现了在抽象工厂中定义的创建产品的方法,生产一组具体产品,这饿产品构件成了一个产品种类,每一个产品都位于某个产品等级结构中,如上述类图中的ConcreteFactoryA和ConcreteFactoryB

AbstractProduct:抽象产品角色,为每种产品声明接口,如图中AbstractProductA、AbstractProductB

ConcreteProduct:具体产品角色,定义了工厂生产的具体产品对象,实现抽象产品接口声明的业务方法,如图中ConcreteProductA1、ConcreteProductA2、ConcreteProductB1、ConcreteProductB2

代码示例

AbstractProduct:



public abstract class Video {
    public abstract void produce();
}

ConcreteProduct:



public class PythonVideo extends Video {
    @Override
    public void produce() {
        System.out.println("Python课程视频");
    }
}


public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("录制Java课程视频");
    }
}

AbstractFactory:



public interface CourseFactory {
    Video getVideo();
    Article getArticle();
 
}

ConcreteFactory:



public class JavaCourseFactory implements CourseFactory {
    @Override
    public Video getVideo() {
        return new JavaVideo();
    }
 
    @Override
    public Article getArticle() {
        return new JavaArticle();
    }
}


public class PythonCourseFactory implements CourseFactory {
    @Override
    public Video getVideo() {
        return new PythonVideo();
    }
 
    @Override
    public Article getArticle() {
        return new PythonArticle();
    }
}
应用场景

使用抽象工厂模式的系统应该符合以下几个条件:

一个系统不要求依赖产品实例如何被创建、组合和表达

这个系统有多个系列产品,而系统中只消费其中某一系列产品

系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现

应用实例

抽象工厂模式的具体源码分析,我们可以来看 java.sql 包下的 Connection 接口,该接口定义了与特定数据库的连接 Connection,执行 SQL statements 并返回 results



public interface Connection  extends Wrapper, AutoCloseable {
    
    Statement createStatement() throws SQLException;
    PreparedStatement prepareStatement(String sql) throws SQLException;
    CallableStatement prepareCall(String sql) throws SQLException;
    DatabaseMetaData getMetaData() throws SQLException;
    Savepoint setSavepoint() throws SQLException;
    Clob createClob() throws SQLException;
    Blob createBlob() throws SQLException;
    SQLXML createSQLXML() throws SQLException;
    // ...省略...
    
}

其中 Statement、PreparedStatement、CallableStatement、DatabaseMetaData、Savepoint、Clob、Blob、SQLXML 等均为接口

我们来看 Statement 接口



public interface Statement extends Wrapper, AutoCloseable {
    
    ResultSet executeQuery(String sql) throws SQLException;
    int executeUpdate(String sql) throws SQLException;
    void close() throws SQLException;
    int getMaxFieldSize() throws SQLException;
    boolean execute(String sql) throws SQLException;
    // ...省略...
    
}

其中的 ResultSet 又是一个接口



public interface ResultSet extends Wrapper, AutoCloseable {
    
    boolean next() throws SQLException;
    void close() throws SQLException;
    boolean wasNull() throws SQLException;
    String getString(int columnIndex) throws SQLException;
    //...省略...
    
}

可以看一下这些接口的实现类

软件设计模式

软件设计模式

软件设计模式

可以看出这里边的抽象工厂模式,Connection 为抽象工厂,工厂方法很多,其中一个抽象产品为 Statement,同时 Statement 也是一个抽象工厂,工厂方法也很多,其中一个抽象产品为 ResultSet,具体工厂和具体产品则为他们的实现类

建造者模式

定义与特点

建造者模式是可以将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一个单独的部分是可以灵活选择的。

结构与实现

建造者模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。

角色说明

建造者模式的主要角色如下。

产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。

抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。

具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。

指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

结构类图

软件设计模式

代码实现

产品角色:包含多个组成部件的复杂对象



public class Product {
 
    List<String> parts = new ArrayList<>();
 
    // 添加产品部件
    public void add(String part) {
        parts.add(part);
    }
    
    public void show() {
        System.out.println("产品创建 ----");
        parts.forEach(System.out::println);
    }
}

抽象建造者:包含创建产品各个子部件的抽象方法



public abstract class Builder {
 
    public abstract void buildPartA();
 
    public abstract void buildPartB();
 
    public abstract void buildPartC();
 
    public abstract Product getResult();
 
}

具体建造者:实现了抽象建造者接口



public class ConcreteBuilder extends Builder {
 
    private Product product = new Product();
 
    @Override
    public void buildPartA() {
        // 这里可以实现 部件的具体细节
        product.add("部件A");
    }
 
    @Override
    public void buildPartB() {
        product.add("部件B");
    }
 
    @Override
    public void buildPartC() {
        product.add("部件C");
    }
 
    @Override
    public Product getResult() {
        return product;
    }
}

指挥者:调用建造者中的方法完成复杂对象的创建



public class Director {
 
    //用来指挥构建过程
    public void construct(Builder builder) {
        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();
    }
}

客户类



public class Product {
 
    List<String> parts = new ArrayList<>();
 
    // 添加产品部件
    public void add(String part) {
        parts.add(part);
    }
 
    public void show() {
        System.out.println("产品创建 ----");
        parts.forEach(System.out::println);
    }
}

应用场景

建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。

创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

比如我们需要设计一个制定旅游计划的程序,旅游计划里面包含有机票,酒店,门票,保险等等,这个流程相对来说是固定的,但是里面具体的细节需要满足客户的不同需求,这时,我们就可以运用建造者模式来设计这个程序。

应用实例

JDK中的StringBuilder源码分析

角色分析

StringBuilder是一个指挥者同时是一个具体建造者

AbstractStringBuilder是Appendable的一个是实现类,为具体建造者

Appendable接口定义了append()方法,是抽象建造者

源码分析

1、创建StringBuilder对象时传入字符串,传入的字符串就相当于指定产品类型,需要构建什么样的产品对象



//构造方法中调用了append()方法,该方法是属于上述模式中的生产方法,所以这里的StringBuilder即是指挥者也是具体建造者
public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}
//append方法中它调用了父类的append()
@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

2、进入到super.append()中,AbstractStringBuilder实现了Appendable,他是具体的建造者,在这里完成了一个具体的操作



//AbstractStringBuilder实现了Appendable,他是具体的建造者,在这里完成了一个具体的操作
abstract class AbstractStringBuilder implements Appendable, CharSequence {    
	public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
}

3. Appendable定义了多个append接口,由他的实现类去实现,方便后续的可扩展性



 public interface Appendable {
    Appendable append(CharSequence csq) throws IOException;
    Appendable append(CharSequence csq, int start, int end) throws IOException;
    Appendable append(char c) throws IOException;
}
注:抽象工厂与建造者模式的区别

抽象工厂实现对产品家族的创建,具有不同分类维度的产品组合,不关心创建的细节,只关心它由什么工厂来创建

建造者模式则是要求按照指定的蓝图建造产品,主要目的是通过组装零配件而产生一个新产品,即更关心建造的流程

原型模式

定义与特点

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。

———《设计模式之美》王争

原型模式的两种实现方法
浅拷贝

浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象。

深拷贝

深拷贝得到的是一份完完全全独立的对象。

深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险。

结构与实现


public class Person implements Cloneable{
    String name = "图图";
    int age = 8;
    Address address = new Address("翻斗花园",888);
 
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
       // person.address = (Address) address.clone(); 实现深拷贝
        return person;
    }
}
 
public class Address implements Cloneable{
    String community;
    int unitNumber;
 
    public Address(String community, int unitNumber) {
        this.community = community;
        this.unitNumber = unitNumber;
    }
 
    @Override
    public String toString() {
        return "Address{" +
                "community='" + community + ''' +
                ", unitNumber=" + unitNumber +
                '}';
    }
 
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
应用场景

需要创建大量的相似对象:如果需要创建大量相似的对象,而且这些对象的区别仅仅在于一些属性的值不同,那么使用原型模式可以减少重复创建对象的过程,提高性能。

对象的创建过程比较耗时或者复杂:如果对象的创建过程比较耗时或者复杂,那么使用原型模式可以避免重复的创建过程,从而提高系统的响应速度。

隐藏对象的创建细节:如果对象的创建细节比较复杂,而且希望将这些细节对客户端隐藏起来,那么使用原型模式可以达到这个目的,客户端只需要关心如何使用这些对象即可。

需要动态地添加或删除对象:如果需要动态地添加或删除对象,而且这些对象都是相似的,那么使用原型模式可以非常方便地实现这个功能。

总之,原型模式适用于需要大量创建相似对象、隐藏对象创建细节或者动态添加或删除对象的场景。它可以有效地减少系统资源的占用,并提高系统的性能和响应速度。

结构型模式概述

结构型模式主要涉及如何组合各种对象以便获得更好、更灵活的结构。虽然面向对象的继承机制提供了最基本的子类扩展父类的功能,但结构型模式不仅仅简单地使用继承,而更多地通过组合与运行期的动态组合来实现更灵活的功能。

更加偏重于类和对象组合设计,以方便代码的“高内聚,低耦合”,拥有更好的延展性

代理模式

定义与特点

定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介,用以达成控制对访问目标的访问控制,这就是代理模式。

代理模式的主要优点有:

代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

代理对象可以扩展目标对象的功能;

代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

其主要缺点是:

在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;

增加了系统的复杂度;

结构与实现

结构:主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。

软件设计模式

实现:



 
public class ProxyTest
{
    public static void main(String[] args)
    {
        Proxy proxy=new Proxy();
        proxy.Request();
    }
}
//抽象主题
interface Subject
{
    void Request();
}
//真实主题
class RealSubject implements Subject
{
    public void Request()
    {
        System.out.println("访问真实主题方法...");
    }
}
//代理类
class Proxy implements Subject
{
    private RealSubject realSubject;
    public void Request()
    {
        if (realSubject==null)
        {
            realSubject=new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }
}
应用场景

常见应用场景

远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。

虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。

安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。

智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。

延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate中就存在属性的延迟加载和关联表的延时加载

实际上远不止以上几种,还有防火墙(Firewall)代理,同步化(Synchronization)代理等等

应用实例

最为典型的代理模式的运用为spring AOP。在开发中,我们通常将某段重复运用的代码抽象成一个方法,然后需要时调用,但是当需求改变,我们就需要对该方法进行更改,此时我们可以运用AOP来实现:创建需要更改方法的代理类,在代理类执行方法前后执行我们需要的业务逻辑。

其中分为静态代理和动态代理,静态主要是在已知代理类的情况下进行调用,动态则为不知道代理类的情况下,在代码运行过程中去创建代理类进行调用,以避免代理类存在过多的情况。



//接口
public interface TargetInterface{
     public void handler();
}
//目标类
public class TargetSource implements TargetInterface{
	public void handler(){
	}
}
//代理类
public class ProxyTarget implements TargetInterface{
	private TargetSource target;
	public ProxyTarget(TargetInterface target){
		this.target = target;
	}
	public void handler(){
		preHandler();
		this.target.handler();
	}
	private void preHandler(){
	}
}


public class MethodInvocation implements InvocationHandler{
	Object target;
	public MethodInvocation(Object target){
		this.target = target;
	}
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	    //TODO 前置执行你的业务(方法)
	    method.invoke(target, args);
	    //TODO 后置执行你的业务(方法)
	}
}
 
//客户端
public class client{
	public static void main(String[] args) {
        TargetInterface target= new TargetSource();
        InvocationHandler handler = new MethodInvocation (target);
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
		proxy.handler();
    }
 
}

ps:

1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。

2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

适配器模式

定义与特点

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

涉及的三个角色

源(Adaptee):需要被适配的对象或类型。

适配器(Adapter):连接目标和源的中间对象。

目标(Target):期待得到的目标。

优点

更好的复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。

更好的扩展性:在实现适配器功能的时候,可以扩展自己源的行为(增加方法),从而自然地扩展系统的功能。

缺点

会导致系统紊乱:滥用适配器,会让系统变得非常零乱。例如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

结构与实现
类适配器模式:

软件设计模式

此处可以看出Adaptee是没有method2的,但是我们需要使用该方法,于是创建适配器Adapter去实现Tagget同时继承Adaptee,达到我们可以使用method2的目的



public class Adaptee {
    public void method1(){
        System.out.println("method 1");
    }
}


public interface Target {
    void method1();
    void method2();
}


public class Adapter extends Adaptee implements Target {
    @Override
    public void method2() {
        System.out.println("method 2");
    }
}
 
// 测试
class AdapterTest {
    public static void main(String[] args) {
        Adapter adapter = new Adapter();
        adapter.method1();
        adapter.method2();
    }
}
对象适配器模式

软件设计模式

与类适配器结构类似,不同在于一个是继承,另一个是采用组合的方式注入。

由于类适配器采用继承,因此无法对其子类进行适配,而对象适配器可以。



public class Adapter implements Target {
 
    private Adaptee adaptee;
 
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
 
    @Override
    public void method1() {
        adaptee.method1();
    }
 
    @Override
    public void method2() {
        System.out.println("method 2");
    }
 
}
 
class AdapterTest {
    public static void main(String[] args) {
        Adapter adapter = new Adapter(new Adaptee());
        adapter.method1();
        adapter.method2();
    }
}
接口适配器模式(缺省适配模式)

为一个接口提供缺省实现,这样子类可以从这个缺省实现扩展,而不需要从原有接口开始扩展,这样可以免去将原有接口方法都实现一遍的麻烦。



public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.addKeyListener(new KeyListener() {
        @Override
        public void keyTyped(KeyEvent e) {}
 
        @Override
        public void keyPressed(KeyEvent e) {
            System.out.println("hey geek!");
        }
 
        @Override
        public void keyReleased(KeyEvent e) {
        }
    });
}


public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            System.out.println("fxcku!");
        }
    });
}
应用场景

支付宝支付、微信支付、网银支付的统一接口实现

系统升级,兼容老版本接口的实现

应用实例

键盘监听器类KeyListener注册进容器后,可以对键盘行为进行监听,但如果要实现该类即意味着要实现所有的方法,十分麻烦,于是创建抽象类KeyAdapter先去实现这些方法,使用时只需要继承该抽象类,实现自己需要使用的方法即可。



public interface KeyListener extends EventListener {
 
    /**
     * Invoked when a key has been typed.
     * See the class description for {@link KeyEvent} for a definition of
     * a key typed event.
     */
    public void keyTyped(KeyEvent e);
 
    /**
     * Invoked when a key has been pressed.
     * See the class description for {@link KeyEvent} for a definition of
     * a key pressed event.
     */
    public void keyPressed(KeyEvent e);
 
    /**
     * Invoked when a key has been released.
     * See the class description for {@link KeyEvent} for a definition of
     * a key released event.
     */
    public void keyReleased(KeyEvent e);
}


public abstract class KeyAdapter implements KeyListener {
    /**
     * Invoked when a key has been typed.
     * This event occurs when a key press is followed by a key release.
     */
    public void keyTyped(KeyEvent e) {}
 
    /**
     * Invoked when a key has been pressed.
     */
    public void keyPressed(KeyEvent e) {}
 
    /**
     * Invoked when a key has been released.
     */
    public void keyReleased(KeyEvent e) {}
}

装饰模式

定义与特点

定义:装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能(既增加其额外功能),同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂

结构与实现

装饰器包含的角色

Component(组件)

组件接口定义了全部组件实现类以及所有装饰器实现的行为。

ConcreteComponent(具体组件实现类)

具体组件实现类实现了Component接口,通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了Component接口中定义的最基本的功能,其他高级功能或者后续添加的新功能,都是通过装饰器方式添加到该类的对象之上的。

Decorator(装饰器)

所有装饰器的父类,他是一个实现了Component接口的抽象类,并在其中封装了一个Component对象,也就是被装饰的对象。而这个被装饰的对象只要是Component类型即可,这就实现了装饰器的组合和复用。

ConcreteDecorator

具体的装饰器实现类,该实现类要向被装饰对象添加某些功能

软件设计模式

1.创建工具接口



/**
* @description: 交通工具接口
* @author: wyq
* @create: 2022-12-13 19:34
**/
public interface Vehicle {
 
    void run();
}

2.创建工具实现类,被装饰对象



/**
 * @description: 交通工具实现类
 * @author: wyq
 * @create: 2022-12-13 19:35
 **/
public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("汽车发动");
    }
}

2.创建工具实现类,被装饰对象



/**
 * @description: 实现了Vehicle接口的抽象装饰类。
 * @author: wyq
 * @create: 2022-12-13 19:37
 **/
public abstract class VehicleDecorator implements Vehicle {
 
    protected Vehicle vehicle;
 
    public VehicleDecorator(Vehicle vehicle) {
        this.vehicle = vehicle;
    }
 
    @Override
    public void run() {
        vehicle.run();
    }
}

2.创建工具实现类,被装饰对象



/**
 * @description: 创建扩展了VehicleDecorator类的实体装饰类。
 * @author: wyq
 * @create: 2022-12-13 19:40
 **/
public class RedPaintDecorator extends VehicleDecorator{
 
    public RedPaintDecorator(Vehicle vehicle) {
        super(vehicle);
    }
 
    @Override
    public void run() {
        vehicle.run();
        setPaint(vehicle);
    }
 
    private void setPaint(Vehicle vehicle){
        System.out.println("红色喷漆");
    }
 
    //使用 RedShapeDecorator 来装饰 Shape 对象。
    public static void main(String[] args) {
        RedPaintDecorator redPaintDecorator = new RedPaintDecorator(new Car());
        redPaintDecorator.run();
    }
}

软件设计模式

应用场景

1、扩展一个类的功能。

2、动态增加功能,动态撤销。

应用实例

软件设计模式

外观模式

定义与特点

定义:

外观模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样可以大大降低应用程序的复杂度,提高了程序的可维护性。

外观模式包含的主要角色:

外观(Facade)角色:为多个子系统对外提供一个共同的接口。

子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

客户(Client)角色:通过一个外观角色访问各个子系统的功能。

结构与实现
类图

软件设计模式

代码实现

子系统角色类:



public class SubSystemOne {
 
    public void methodOne() {
        System.out.println("子系统方法一");
    }
}
 
public class SubSystemTwo {
 
    public void methodTwo() {
        System.out.println("子系统方法二");
    }
}
 
public class SubSystemThree {
 
    public void methodThree() {
        System.out.println("子系统方法三");
    }
 
}
 
public class SubSystemFour {
 
    public void methodFour() {
        System.out.println("子系统方法四");
    }
}

外观角色类:



public class Facade {
 
    private SubSystemOne subSystemOne;
 
    private SubSystemTwo subSystemTwo;
 
    private SubSystemThree subSystemThree;
 
    private SubSystemFour subSystemFour;
 
 
    public Facade() {
        subSystemOne = new SubSystemOne();
        subSystemTwo = new SubSystemTwo();
        subSystemThree = new SubSystemThree();
        subSystemFour = new SubSystemFour();
    }
 
    public void methodA() {
        System.out.println("方法组A");
        subSystemOne.methodOne();
        subSystemTwo.methodTwo();
        subSystemFour.methodFour();
    }
 
    public void methodB() {
        System.out.println("方法组B");
        subSystemTwo.methodTwo();
        subSystemThree.methodThree();
    }
}

客户角色类:



public class Client {
 
    public static void main(String[] args) {
        // 实例化外观接口
        Facade facade = new Facade();
        facade.methodA();
 
        facade.methodB();
    }
}

优点:

降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。

对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

缺点:

不能很好地限制客户使用子系统类,容易给系统带来未知风险。

增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

应用场景

对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。

当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。

当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

享元模式

定义与特点

定义:

享元模式是一种能够通过运用共享技术来有效地支持大量细粒度对象的复用的设计模式。

优点:

享元模式可以通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

缺点:

使用享元模式需要维护一个记录了系统已有地所有享元地列表,需要耗费资源。另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,使得程序地逻辑复杂化。因此应当在有足够多地对象实例可供共享时才值得使用享元模式。

结构与实现
享元模式涉及到的角色

FlyWeight:抽象的享元角色,产品的抽象类,定义了对象的外部状态和内部状态的接口或实现

ConcreteFlyWeight:具体的享元角色,产品的实现类,实现了抽象角色定义的相关业务

UnSharedConcreteFlyWeight:不可共享的角色,一般不会出现在享元工厂中

FlyWeightFactory:享元工厂类,内部提供一个池容器,储存ConcreteFlyWeight,同时提供从池中存取数据的操作

类图
代码实现

用户类:

用于区分不同网站的客户账号,是网站的外部状态



public class User {
 
    private String name;
 
    public User(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

网站抽象类:



public abstract class WebSite {
 
    public abstract void use(User user);
}

具体网站类:



public class ConcreteWebSite extends WebSite {
 
    private String name = "";
 
    public ConcreteWebSite(String name) {
        this.name = name;
    }
 
    @Override
    public void use(User user) {
        System.out.println("网站分类:" + name + " 用户:" + user.getName());
    }
}

网站工厂类:



public class WebSiteFactory {
 
    private Hashtable flyweights = new Hashtable();
 
    public WebSite getWebSiteCategory(String key) {
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new ConcreteWebSite(key));
        }
        return (WebSite) flyweights.get(key);
    }
 
    // 获取网站分类总数
    public int getWebSiteCount() {
        return flyweights.size();
    }
}

客户端代码:



public class Client {
 
    public static void main(String[] args) {
        WebSiteFactory f = new WebSiteFactory();
 
        WebSite fx = f.getWebSiteCategory("产品展示");
        fx.use(new User("小菜"));
 
        WebSite fy = f.getWebSiteCategory("产品展示");
        fy.use(new User("大鸟"));
 
        WebSite fz = f.getWebSiteCategory("产品展示");
        fz.use(new User("娇娇"));
 
        WebSite fl = f.getWebSiteCategory("博客");
        fl.use(new User("老顽童"));
 
        WebSite fm = f.getWebSiteCategory("博客");
        fm.use(new User("桃谷六仙"));
 
        WebSite fn = f.getWebSiteCategory("博客");
        fn.use(new User("南海鳄神"));
 
        System.out.println("得到的网站分类总数为:" + f.getWebSiteCount());
 
    }
}

结果:

软件设计模式

结果显示,尽管给6个不同的用户使用网站,但实际上只有2个网站实例

应用场景

享元模式常用于系统底层开发,解决系统的性能问题,是池技术的重要实现方式

常见池技术有:

String 常量池

数据库连接池

缓冲池

如数据库连接池,池中是创建好的连接对象,若池中有符合需求的对象时直接用,避免重新创建;若池中没有符合需求的,则创建一个。

组合模式

定义与特点

将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户能以一致的方式处理个别对象和组合对象。(一致的方式:类似公司组织,请假等流程都是层层上报,程序基本相同)

软件设计模式

组合模式的构成

抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。我们一般认为树状结构的每个节点都是一样的,但是也有不一样的情况,这里相当于树状节点的父级,这样来看每个节点就都一样了,一般都有添加、删除、和动作(该树形结构的功能)方法。(相当于上图的root)

树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。这是树状结构最底层那个,没有下级了。

树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。除了最底层之外的节点。(相当于上图的branch)

优点:

组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;

更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

缺点:

设计较复杂,客户端需要花更多时间理清类之间的层次关系;

不容易限制容器中的构件;

不容易用继承的方法来增加构件的新功能。

结构与实现


/**
 * 这里使用接口或者抽象类都可以的
 **/
public abstract class Region {
    /**
     * 添加节点
     * @param region
     */
    abstract void add(Region region);
 
    /**
     * 删除节点
     * @param region
     */
    abstract void remove(Region region);
 
    /**
     * 获取当前节点下面的节点
     * @param i
     * @return
     */
    abstract Region getChild(int i);
}


/**
 * 树叶地区
 **/
public class LeafRegion extends Region{
 
    private  String name;
 
    public LeafRegion(String name) {
        this.name = name;
    }
 
    @Override
    void add(Region region) {
		//叶子节点没有下级
    }
 
    @Override
    void remove(Region region) {
		//叶子节点没有下级
    }
 
    @Override
    Region getChild(int i) {
    		//叶子节点没有下级
        return null;
    }
 
    @Override
    void notice() {
        /**
         * 最底层的接到了通知
         */
    }
}


/**
 * 树枝地区
 **/
public class CompositeRegion extends Region{
 
    private String name;
 
    //用来盛放子节点
    private List<Region> children = new ArrayList<>();
 
    @Override
    void add(Region region) {
        children.add(region);
    }
 
    @Override
    void remove(Region region) {
        children.remove(region);
    }
 
    @Override
    Region getChild(int i) {
        return null;
    }
 
    @Override
    void notice() {
        /*通知下级所有的部门,下级如果是树枝的话继续通知,这是个递归操作*/
        for (Region child : children) {
            child.notice();
 
        }
    }
}
应用场景

主要用于一些树形菜单文件的实现,还有一些数据下钻的场景也会用到

应用实例

java集合类HashMap是采用数组+链表的结构组合而成的,此处数组的每一个值都可以看做一个根节点,而数组上的链表节点作为枝和叶节点、

软件设计模式



   static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
 
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
   }

桥接模式

定义与特点

一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。通过组合关系来替代继承关系,避免继承层次的指数级爆炸。

结构与实现

软件设计模式

抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。

扩展抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。

实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。

具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。

软件设计模式



public abstract class Pen {
    protected Color color;
    public void setColor(Color color){
        this.color = color;
    }
    public abstract void draw(String name);
}
 
public class SmallPen extends Pen{
    @Override
    public void draw(String name) {
        String penType="小号毛笔绘制";
        this.color.bepaint(penType,name);
    }
}
 
public class MiddlePen extends Pen{
    @Override
    public void draw(String name) {
        String penType="中号毛笔绘制";
        this.color.bepaint(penType,name);
    }
}
 
public class BigPen extends Pen{
    @Override
    public void draw(String name) {
        String penType="大号毛笔绘制";
        this.color.bepaint(penType,name);
    }
}
 
public interface Color {
    void bepaint(String penType,String name);
}
 
public class Red implements Color{
 
    @Override
    public void bepaint(String penType, String name) {
        System.out.println(penType + "红色的"+ name + ".");
    }
}
 
public class Green implements Color{
    @Override
    public void bepaint(String penType, String name) {
        System.out.println(penType + "绿色的"+ name + ".");
    }
}
 
public class Client {
    public static void main(String[] args) {
        Color color = new Red();
        Pen pen = new BigPen();
 
        pen.setColor(color);
        pen.draw("鲜花");
    }
}
应用场景

当一个对象有多个变化因素时,考虑依赖于抽象的实现,而不是具体的实现。如手机品牌有两种变化因素,一个是品牌,一个是功能;

当我们考虑一个对象的多个变化因素可以动态变化的时候,考虑使用桥接模式。比如手机,手机的功能是可以变的,品牌也是可以变化的,所以将它们分离出来,独立的变化。

行为型模式概述

行为型模式通过控制多个类和对象之间相互协作来共同完成单个对象都无法完成的任务,以实现对象间解耦的目的,它涉及算法以及对象间职责的划分。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间的协作行为,后者采用组合和聚合模式在对象间的协作行为。由于组合、聚合关系比继承关系耦合度低,满足且满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

策略模式

定义与特点

定义:

在策略模式中,定义了一系列策略算法,并将每个策略算法封装起来,使它们可以在调用的时候相互替换而不影响调用方。策略模式通过对策略算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

优点:

1、策略类之间可以自由切换。由于策略类都实现同一个接口,所以使它们之间可以自由切换。

2、易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码。

3、避免使用多重条件选择语句,充分体现面向对象设计思想。

缺点:

1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类。

2、策略模式会造成很多的策略类。

结构与实现

角色

解释

抽象策略角色(Strategy)

这是一个抽象角色,通常由一个接口或抽象类来实现。此角色给出所有具体策略类所需实现的接口。

具体策略角(ConcreteStrategy)

包装了相关算法或行为。

环境角色(Context)

持有一个Strategy类的引用,调用具体策略方法

软件设计模式

创建一个接口。



public interface Strategy {
   public int doOperation(int num1, int num2);

创建实现接口的实体类。



public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}


public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}


public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

创建 Context 类。



public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

使用 Context 来查看当它改变策略 Strategy 时的行为变化。



public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

应用场景

1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

2、一个系统需要动态地在几种算法中选择一种。

3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

应用实例

Spring在实例化对象的时候会利用策略者模式完成实例化的过程。主要是有两个策略实现类,但是Spring真正用到的是CglibSubclassingInstantiationStrategy,它对其父类(SimpleInstantiationStrategy)的实例化功能做了拓展。SimpleInstantiationStrategy的职责是判断是否有 MethodOverrides,如果没有则是直接使用反射,如果有就根据子类(CglibSubclassingInstantiationStrategy)的拓展功能利用cglib创建代理类再反射实例化对象。

软件设计模式

观察者模式

定义与特点

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都可以得到通知并自动更新。

优点:观察者与被观察者之间是一个轻度的关联关系,体现了模块的高内聚,低耦合。因此对于观察者和被观察者来说,要进行扩展就比较简单了。被观察者并不需要认识任何一个具体的观察者。它只需要提供一个观察者共同的接口即可。提高代码的可复用性。

缺点:在状态变化,观察者通知被观察者的时候,如果观察者过多,那么可能导致消息的传送失去实时性。

结构与实现

软件设计模式

创建Subject类



import java.util.ArrayList;
import java.util.List;
 
public class Subject {
   
   private List<Observer> observers 
      = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
 
   public void attach(Observer observer){
      observers.add(observer);      
   }
 
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

创建Observer类



public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

创建实体观察者类



public class BinaryObserver extends Observer{
 
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Binary String: " 
      + Integer.toBinaryString( subject.getState() ) ); 
   }
}
 
 
 
 
 
public class OctalObserver extends Observer{
 
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
     System.out.println( "Octal String: " 
     + Integer.toOctalString( subject.getState() ) ); 
   }
}
 
 
 
 
public class HexaObserver extends Observer{
 
   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Hex String: " 
      + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}

使用Subject和实体观察者对象



public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
 
      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}
应用场景

当你的业务符合订阅-发布这种场景时考虑这个模式。

一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。

应用实例

1、最常见的有消息队列中常常使用发布订阅模式来进行消息的生产和提醒订阅者去消费消息

2、Java JDK中的java.util.Observable类(被观察者)和java.util.Observer接口(观察者)

我们通过实现Observer接口来进行创建观察者来进行事件的监听,同时我们可以通过继承Observable类来重写被观察者的功能,下图为观察者模式下的类图。

软件设计模式

迭代器模式

定义与特点

用于顺序访问集合对象中的元素,但又不需要知道集合对象的底层表示和内部细节。

优点: 它支持以不同的方式遍历一个聚合对象。

迭代器简化了聚合类。

在同一个聚合上可以有多个遍历。

在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

结构与实现

软件设计模式

1、创建接口



public interface Iterator {
   public boolean hasNext();
   public Object next();
}


public interface Container {
   public Iterator getIterator();
}

2、创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator。



public class NameRepository implements Container {
   public String[] names = {"Robert" , "John" ,"Julie" , "Lora"};
 
   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }
 
   private class NameIterator implements Iterator {
 
      int index;
 
      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }
 
      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }     
   }
}

3、使用 NameRepository 来获取迭代器,并打印名字。



public class IteratorPatternDemo {
   
   public static void main(String[] args) {
      NameRepository namesRepository = new NameRepository();
 
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      }  
   }
}
应用场景

访问一个聚合对象的内容而无须暴露它的内部表示。

需要为聚合对象提供多种遍历方式。

为遍历不同的聚合结构提供一个统一的接口。

应用实例

在java中我们在处理集合的时候常常使用到其中内嵌的迭代器,该迭代器就使用迭代器模式实现内部元素的遍历,通过调用Arraylist的Iterator来获取迭代器,并进行遍历。

软件设计模式

模板方法模式

定义与特点

模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行

优点:

封装不变部分, 扩展可变部分

把认为是不变部分的算法封装到父类实现, 而可变部分的则可以通过继承来继续扩展。

提取公共部分代码, 便于维护

行为由父类控制, 子类实现

基本方法是由子类实现的, 因此子类可以通过扩展的方式增加相应的功能, 符合开闭原则。

缺点:

子类执行的结果影响了父类的结果,这和我们平时设计习惯颠倒了,在复杂项目中,会带来阅读上的难度。

代码平时代码返回由下至上,比如service和serviceImpl,逻辑在impl完成,但模板方法在impl返回后还要进行处理。

可能引起子类泛滥和为了继承而继承的问题

结构与实现

软件设计模式

AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法

ConcreteClass 实现抽象方法, 以完成算法中特定子类的具体业务步骤



public abstract class AbstractClass {
    // 共同的且繁琐的操作
    private void baseOperation() {
        // do something
    }
 
    // 由子类定制的操作
    protected abstract void customOperation();
 
    // 模板方法定义的框架
    public final void templateMethod() {
        /**
         * 调用基本方法,完成固定逻辑
         */
        baseOperation();
        customOperation();
    }
 
}


public class ConcreteClass1 extends AbstractClass{
 
    @Override
    protected void customOperation() {
        // 具体模板1 业务逻辑
        System.out.println("具体模板1:customOperation()");
    }
}
应用场景

简单举例,制作不同口味的冰淇淋,步骤一致,调味原料不同。

写技术方案,模板框架目录大致相同,内容不同

应用实例

在Map接口中,没有定义get、put和remove的具体操作,其具体操作由子类来实现,而对于putIdAbsent、getOrDefault等方式需要使用到get、put和remove方法,这就是典型的模板方法设计模式,对于putIdAbsent、getOrDefault等default方法是在JDK8中加入的方法,可以提供更为实用的方法,而在子类中不需要任何改动就可以实现。



public interface Map<K,V> {
    //省略了其他不重要的方法
 
    V remove(Object key);
 
    V get(Object key);
 
    V put(K key, V value);
 
    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
    }
 
    default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }
 
        return v;
    }
    default boolean remove(Object key, Object value) {
        Object curValue = get(key);
        if (!Objects.equals(curValue, value) ||
            (curValue == null && !containsKey(key))) {
            return false;
        }
        remove(key);
        return true;
    }
}


public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
 
    private static final long serialVersionUID = 362498820763181265L;
 
 
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
 
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
 
        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
 
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
 
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
 
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
 
 
    //简单看一个remove的子类实现,其他方法类似
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);   //看这里---------------
                return node;
            }
        }
        return null;
    }
 
 
    }

命令模式

定义与特点

定义:命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

优点: 降低了系统耦合度,新的命令可以很容易添加到系统中去。

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

结构与实现

角色

解释

请求角色

包含具体要实现的功能

具体命令角色

实现请求中对应的具体功能

调度者

统一调用执行所有命令

软件设计模式

1.创建一个命令接口。



public interface Order {
    void execute();
}

2.创建一个请求类。



public class Stock {
   
   private String name = "ABC";
   private int quantity = 10;
 
   public void buy(){
      System.out.println("Stock [ Name: "+name+", 
         Quantity: " + quantity +" ] bought");
   }
   public void sell(){
      System.out.println("Stock [ Name: "+name+", 
         Quantity: " + quantity +" ] sold");
   }
}

3.创建实现了 Order 接口的实体类。



public class BuyStock implements Order {
   private Stock abcStock;
 
   public BuyStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.buy();
   }
}


public class SellStock implements Order {
   private Stock abcStock;
 
   public SellStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.sell();
   }
}

4.创建命令调用类



import java.util.ArrayList;
import java.util.List;
 
public class Broker {
   private List<Order> orderList = new ArrayList<Order>(); 
 
   public void takeOrder(Order order){
      orderList.add(order);      
   }
 
   public void placeOrders(){
      for (Order order : orderList) {
         order.execute();
      }
      orderList.clear();
   }
}

5.使用 Broker 类来接受并执行命令。



public class CommandPatternDemo {
   public static void main(String[] args) {
      Stock abcStock = new Stock();
 
      BuyStock buyStockOrder = new BuyStock(abcStock);
      SellStock sellStockOrder = new SellStock(abcStock);
 
      Broker broker = new Broker();
      broker.takeOrder(buyStockOrder);
      broker.takeOrder(sellStockOrder);
 
      broker.placeOrders();
   }
}
应用场景

认为是命令的地方都可以使用命令模式

1、GUI 中每一个按钮都是一条命令。

2、模拟 CMD。

应用实例

责任链模式

定义与特点

定义:责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

优点:

降低耦合度。它将请求的发送者和接收者解耦。

增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。

增加新的请求处理类很方便。

缺点:

不能保证请求一定被接收。

系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。

可能不容易观察运行时的特征,有碍于除错。

结构与实现

软件设计模式

应用场景

有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

可动态指定一组对象处理请求。



public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;
 
   protected int level;
 
   //责任链中的下一个元素
   protected AbstractLogger nextLogger;
 
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
 
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }
 
   abstract protected void write(String message);
   
}


public class ConsoleLogger extends AbstractLogger {
 
   public ConsoleLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}


public class ErrorLogger extends AbstractLogger {
 
   public ErrorLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}


public class FileLogger extends AbstractLogger {
 
   public FileLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}


public class ChainPatternDemo {
   
   private static AbstractLogger getChainOfLoggers(){
 
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
 
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
 
      return errorLogger;  
   }
 
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
 
      loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
 
      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is a debug level information.");
 
      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}

状态模式

定义与特点

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

结构与实现

软件设计模式

抽象出来状态变化的接口



public interface State {
   public void doAction(Context context);
}

创建实现接口的实体类



public class StartState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in start state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Start State";
   }
}
 
 
 
public class StopState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in stop state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Stop State";
   }
}

创建Context类用来观察state类的状态变化



public class Context {
   private State state;
 
   public Context(){
      state = null;
   }
 
   public void setState(State state){
      this.state = state;     
   }
 
   public State getState(){
      return state;
   }
}

通过调用Context类来实现状态的转变



public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      StartState startState = new StartState();
      startState.doAction(context);
 
      System.out.println(context.getState().toString());
 
      StopState stopState = new StopState();
      stopState.doAction(context);
 
      System.out.println(context.getState().toString());
   }
}
应用场景

行为随状态改变而改变的场景。

条件、分支语句的代替者。

中介者模式

定义与特点

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。

缺点:中介者会庞大,变得复杂难以维护。

结构与实现

软件设计模式

创建中介类



import java.util.Date;
 
public class ChatRoom {
   public static void showMessage(User user, String message){
      System.out.println(new Date().toString()
         + " [" + user.getName() +"] : " + message);
   }
}

创建用户类



public class User {
   private String name;
 
   public String getName() {
      return name;
   }
 
   public void setName(String name) {
      this.name = name;
   }
 
   public User(String name){
      this.name  = name;
   }
 
   public void sendMessage(String message){
      ChatRoom.showMessage(this,message);
   }
}

使用 User 对象来显示他们之间的通信



public class MediatorPatternDemo {
   public static void main(String[] args) {
      User robert = new User("Robert");
      User john = new User("John");
 
      robert.sendMessage("Hi! John!");
      john.sendMessage("Hello! Robert!");
   }
}
应用场景

系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。

想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

访问者模式

定义与特点

一个作用于某对象结构中的各元素的操作,它可以使你在不改变各元素的类的前提下定义作用于这些元素的新操作。

注:

单分派:执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定。

双分派:执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。

解决的痛点:访问者模式解决的痛点主要是需要动态绑定的类型,所以调用哪个重载版本,其参数中的子类必须传入静态类型为目标子类的参数,并在方法中使用传入参数的动态绑定。

结构与实现

软件设计模式

Visitor:是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作;

ConcreteVisitor:具体访问者,实现每个由Visitor声明的操作,是每个操作实现的部分;

ObjectStructure:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素;

Element:定义一个accept方法,接收一个访问者对象;

ConcreteElement:为具体元素,实现了accept方法。

实现

假设从网站上爬取了很多资源文件,它们的格式有三种:PDF、PPT、Word。我们现在要开发一个工具来处理这批资源文件,把这些资源文件中的文本内容抽取出来放到 txt 文件中。代码如下:



public abstract class ResourceFile {
  protected String filePath;
 
  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
 
  public abstract void extract2txt();
}
 
public class PPTFile extends ResourceFile {
  public PPTFile(String filePath) {
    super(filePath);
  }
 
  @Override
  public void extract2txt() {
    System.out.println("Extract PPT.");
  }
}
 
public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }
 
  @Override
  public void extract2txt() {
 
    System.out.println("Extract PDF.");
  }
}
 
public class WordFile extends ResourceFile {
  public WordFile(String filePath) {
    super(filePath);
  }
 
  @Override
  public void extract2txt() {
    System.out.println("Extract WORD.");
  }
}
 
public class ToolApplication {
  public static void main(String[] args) {
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      resourceFile.extract2txt();
    }
  }
 
  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

如果工具的功能不停地扩展,不仅要能抽取文本内容,还要支持压缩、提取文件元信息(文件名、大小、更新时间等等)构建索引等一系列的功能,那如果我们继续按照上面的实现思路,就会存在这样几个问题:违背开闭原则,添加一个新的功能,所有类的代码都要修改;虽然功能增多,每个类的代码都不断膨胀,可读性和可维护性都变差了;把所有比较上层的业务逻辑都耦合到 PdfFile、PPTFile、WordFile 类中,导致这些类的职责不够单一,变成了大杂烩。

针对上面的问题,我们常用的解决方法就是拆分解耦,把业务操作跟具体的数据结构解耦,设计成独立的类。



public abstract class ResourceFile {
  protected String filePath;
  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
}
 
public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }
  //...
}
//...PPTFile、WordFile代码省略...
public class Extractor {
  public void extract2txt(PPTFile pptFile) {
    //...
    System.out.println("Extract PPT.");
  }
 
  public void extract2txt(PdfFile pdfFile) {
    //...
    System.out.println("Extract PDF.");
  }
 
  public void extract2txt(WordFile wordFile) {
    //...
    System.out.println("Extract WORD.");
  }
}
 
public class ToolApplication {
  public static void main(String[] args) {
    Extractor extractor = new Extractor();
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      extractor.extract2txt(resourceFile);
    }
  }
 
  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}

多态是一种动态绑定,可以在运行时获取对象的实际类型,来运行实际类型对应的方法。而函数重载是一种静态绑定,在编译时并不能获取对象的实际类型,而是根据声明类型执行声明类型对应的方法。所以上面的代码是编译通过不了的,第 37 行会报错。

软件设计模式



public abstract class ResourceFile {
  protected String filePath;
  public ResourceFile(String filePath) {
    this.filePath = filePath;
  }
  abstract public void accept(Visitor vistor);
}
 
public class PdfFile extends ResourceFile {
  public PdfFile(String filePath) {
    super(filePath);
  }
 
  @Override
  public void accept(Visitor visitor) {
    visitor.visit(this);
  }
}
 
public interface Visitor {
  void visit(PdfFile pdfFile);
  void visit(PPTFile pdfFile);
  void visit(WordFile pdfFile);
}
 
public class Extractor implements Visitor {
  @Override
  public void visit(PPTFile pptFile) {
    System.out.println("Extract PPT.");
  }
 
  @Override
  public void visit(PdfFile pdfFile) {
    System.out.println("Extract PDF.");
  }
 
  @Override
  public void visit(WordFile wordFile) {
    System.out.println("Extract WORD.");
  }
}
 
public class Compressor implements Visitor {
  @Override
  public void visit(PPTFile pptFile) {
    System.out.println("Compress PPT.");
  }
 
  @Override
  public void visit(PdfFile pdfFile) {
    System.out.println("Compress PDF.");
  }
 
  @Override
  public void visit(WordFile wordFile) {
    System.out.println("Compress WORD.");
  }
 
}
 
public class ToolApplication {
  public static void main(String[] args) {
    Extractor extractor = new Extractor();
    List<ResourceFile> resourceFiles = listAllResourceFiles(args[0]);
    for (ResourceFile resourceFile : resourceFiles) {
      resourceFile.accept(extractor);
    }
 
    Compressor compressor = new Compressor();
    for(ResourceFile resourceFile : resourceFiles) {
      resourceFile.accept(compressor);
    }
  }
 
  private static List<ResourceFile> listAllResourceFiles(String resourceDirectory) {
    List<ResourceFile> resourceFiles = new ArrayList<>();
    resourceFiles.add(new PdfFile("a.pdf"));
    resourceFiles.add(new WordFile("b.word"));
    resourceFiles.add(new PPTFile("c.ppt"));
    return resourceFiles;
  }
}
应用场景

需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使得你可以将相关的操作集中起来 定义在一个类中。(单分派)

备忘录模式

定义与特点

备忘录模式(Memento Pattern),是行为型模式设计模式之一,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护被保存的这些对象状态的完整性以及内部实现不向外暴露。

结构与实现
涉及到的角色

发起人角色(Originator):负责创建一个备忘录,可以记录、恢复自身的内部状态,同时 发起人 还可以根据需要决定 备忘录中 存储自身的哪些内部状态。

备忘录角色(Memento):备忘录角色用于存储 发起人 的内部状态,并且可以防止 发起人 以外的对象访问 本备忘录。

管理者角色(CareTaker):负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。

类图

软件设计模式

代码实现

发起人角色类



public class Originator {
    /**
     * 发起人内部状态
     */
    private String state;
 
    // 创建一个备忘录
    public Memento createMemento() {
        return new Memento(this.state);
    }
 
    // 从备忘录恢复
    public void restoreMemento(Memento memento) {
        this.setState(memento.getState());
    }
 
    public String getState() {
        return state;
    }
 
    public void setState(String state) {
        this.state = state;
    }
}

备忘录角色



public class Memento {
 
    /**
     * 发起人需要备忘的内部状态
     */
    private String state;
 
    public String getState() {
        return state;
    }
 
    public void setState(String state) {
        this.state = state;
    }
 
    public Memento(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();
        originator.setState("火热");
        System.out.println("初始状态:" + originator.getState());
        //来一个备忘录管理员
        Caretaker caretaker = new Caretaker();
        //管理员存储发起人的备忘录
        caretaker.setMemento(originator.createMemento());
        originator.setState("低迷");
        System.out.println("过程中状态:" + originator.getState());
 
        //发起人从管理员获取备忘录进行回滚
        originator.restoreMemento(caretaker.getMemento());
 
        System.out.println("回滚后的状态:" + originator.getState());
 
    }
}

运行结果:

软件设计模式

应用场景

打游戏时的存档。

Windows 里的 ctrl + z。

浏览器中的后退。

数据库的事务管理。

解释器模式

定义与特点

定义:解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

附:

解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。解释器模式的代码实现比较灵活,没有固定的模板。设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。

优点:

可扩展性比较好,灵活。

增加了新的解释表达式的方式。

易于实现简单文法。

缺点:

可利用场景比较少。

对于复杂的文法比较难维护。

解释器模式会引起类膨胀。

结构与实现

软件设计模式

应用场景

1、一些重复出现的问题可以用一种简单的语言来进行表达。

2、一个简单语法需要解释的场景。



public interface Expression {
    public boolean interpret(String context);
}


public class TerminalExpression implements Expression {
   
   private String data;
 
   public TerminalExpression(String data){
      this.data = data; 
   }
 
   @Override
   public boolean interpret(String context) {
      if(context.contains(data)){
         return true;
      }
      return false;
   }
}


public class OrExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public OrExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) || expr2.interpret(context);
   }
}


public class AndExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public AndExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) && expr2.interpret(context);
   }
}

InterpreterPatternDemo 使用 Expression 类来创建规则,并解析它们。



public class InterpreterPatternDemo {
 
   //规则:Robert 和 John 是男性
   public static Expression getMaleExpression(){
      Expression robert = new TerminalExpression("Robert");
      Expression john = new TerminalExpression("John");
      return new OrExpression(robert, john);    
   }
 
   //规则:Julie 是一个已婚的女性
   public static Expression getMarriedWomanExpression(){
      Expression julie = new TerminalExpression("Julie");
      Expression married = new TerminalExpression("Married");
      return new AndExpression(julie, married);    
   }
 
   public static void main(String[] args) {
      Expression isMale = getMaleExpression();
      Expression isMarriedWoman = getMarriedWomanExpression();
 
      System.out.println("John is male? " + isMale.interpret("John"));
      System.out.println("Julie is a married women? " 
      + isMarriedWoman.interpret("Married Julie"));
   }
}
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...