设计模式 学习笔记

75 阅读18分钟

设计模式之美

如何评判代码质量

可维护性、可读性、可拓展性、灵活性、简洁性、可复用性、可测试性(单元测试)

设计模式分类

  1. 创建型

    常用: 单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式

    不常用:原型模式

  2. 结构型

    常用:代理模式、桥接模式、装饰者模式、适配器模式

    不常用:门面模式、组合模式、享元模式

  3. 行为型

    常用:观察者模式、模板模式、策略模式、指责链模式、迭代器模式、状态模式

    不常用:访问者模式、备忘录模式、命令模式、解释器模式、中介模式

抽象

屏蔽不需要关注的实现细节

继承

复用代码,但是深层次继承则不利于代码的阅读。

继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性

多态

子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现

public SortedDynamicArray extends DynamicArray() {
  public void test() {
    //子类方法
  }
}
​
DynamicArray dynamicArray = new SortedDynamicArray();    
dynamicArray.test(); //调用SortedDynamicArray类的方法,也即子类的方法;

接口的多态:

public interface Iterator {
​
    boolean hasNext();
​
    String next();
​
    String remove();
}
​
public class Array implements Iterator{
    private String[] data;
​
​
    @Override
    public boolean hasNext() {
        return true;
    }
​
    @Override
    public String next() {
        return null;
    }
​
    @Override
    public String remove() {
        return null;
    }
}
​
public class LinkedList implements Iterator{
    @Override
    public boolean hasNext() {
        return true;
    }
​
    @Override
    public String next() {
        return null;
    }
​
    @Override
    public String remove() {
        return null;
    }
}
​
public class Test {
    private static void print(Iterator iterator) {
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
​
    public static void main(String[] args) {
        Iterator arrayIterator = new Array();
        print(arrayIterator);
        Iterator linkedListIterator = new LinkedList();
        print(linkedListIterator);
    }
}

当再增加一种要遍历打印的类型的时候,比如HashMap,我们只需让 HashMap 实现 Iterator 接口,重新实现自己的 hasNext()、next()等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性

面向对象四大特性

封装:加装备(添加盔甲)

继承:师傅掌对掌传输武功(毫无保留)

抽象:从道到术,柳叶能伤人

多态:奥特曼变身

基于接口编程

“基于接口而非实现编程” 这条原则的另一个表述方式,是 “基于抽象而非实现编程”

越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。

  1. 函数的命名不能暴露任何实现细节。比如,前面提到的 uploadToAliyun() 就不符合要求,应该改为去掉 aliyun 这样的字眼,改为更加抽象的命名方式,比如:upload()。

    涉及图片处理和存储的业务逻辑实践:

public class Image {
}
​
/**
 * @Description: 整个上传流程包含三个步骤:创建 bucket(你可以简单理解为存储目录)、
 * 生成 access token 访问凭证、携带 access token 上传图片到指定的 bucket 中。
 * 代码实现非常简单,类中的几个方法定义得都很干净,用起来也很清晰,乍看起来没有太大问题,
 * 完全能满足我们将图片存储在阿里云的业务需求。
 * @Author: yanzhihao
 * @Date: 2022/1/20 2:34 下午
 */
public class AliyunImageStore {
    public void createBucketIfNotExisting(String bucketName) {
        // ... 创建 bucket 代码逻辑...
        // ... 失败会抛出异常..
    }
​
    public String generateAccessToken() {
        // ... 根据 accesskey/secrectkey 等生成 access token
        return "";
    }
​
    public String uploadToAliyun(Image image, String bucketName, String accessTok) {
        //... 上传图片到阿里云...
        // ... 返回图片存储在阿里云上的地址 (url)...
        return "";
    }
​
    public Image downloadFromAliyun(String url, String accessToken) {
        //... 从阿里云下载图片...
        return null;
    }
}
​
public class ImageProcessingJob {
    private static final String BUCKET_NAME = "ai_images_bucket";
​
    public void process() {
        Image image = new Image(); //处理图片,并封装为Image对象;
        AliyunImageStore imageStore = new AliyunImageStore();
        imageStore.createBucketIfNotExisting(BUCKET_NAME);
        String accessToken = imageStore.generateAccessToken();
        imageStore.uploadToAliyun(image, BUCKET_NAME, accessToken);
    }
}
​
  1. 过一段时间后,业务场景发生变化,要将图片存储到私有云,而不是阿里云,这时候需要修改的代码就很多了

    基于接口编程的优化方案如下:

    public interface ImageStore {
        String upload(Image image, String bucketName);
        Image download(String url);
    }
    ​
    public class AliyunImageStore implements ImageStore {
        @Override
        public String upload(Image image, String bucketName) {
            // 上传图片到阿里云
            // 返回图片在阿里云的地址
            createBucketIfNotExisting(bucketName);
            return generateAccessToken();
        }
    ​
        @Override
        public Image download(String url) {
            String accessToken = generateAccessToken();
            return null;
        }
    ​
        private void createBucketIfNotExisting(String bucketName) {
            // ... 创建 bucket...
            // ... 失败会抛出异常..
        }
    ​
        private String generateAccessToken() {
            // ... 根据 accesskey/secrectkey 等生成 access token
            return "token";
        }
    }
    ​
    public class PrivateImageStore implements ImageStore{
        @Override
        public String upload(Image image, String bucketName) {
            // 上传图片到私有云
            // 返回图片url
            createBucketIfNotExisting(bucketName);
            return "";
        }
    ​
        @Override
        public Image download(String url) {
            return null;
        }
    ​
        private void createBucketIfNotExisting(String bucketName) {
            // ... 创建 bucket...
            // ... 失败会抛出异常..
        }
    }
    ​
    public class ImageProcessingJob {
    ​
        private static final String BUCKET_NAME = "ai_images_bucket";
            
        // 上传到哪里直接修改imageStore实例化的代码即可
        public void process() {
            Image image = new Image(); //处理图片,并封装为Image对象;
            ImageStore imageStore = new PrivateImageStore();
            imageStore.upload(image, BUCKET_NAME);
        }
    }
    

多用组合少用继承

使用组合时如何像继承复用代码

public interface Flyable {
    void fly();
}
​
public class FlyAbility implements Flyable{
    @Override
    public void fly() {
        // fly
    }
}
​
public class Ostrich implements Flyable{
        //鸵鸟    
  
    private final FlyAbility flyAbility = new FlyAbility();
​
    @Override
    public void fly() {
        flyAbility.fly();
    }
}
​

贫血模型和充血模型(领域驱动设计)

  1. 贫血模型:传统MVC

  2. 充血模型:DDD(适合逻辑复杂的系统)

    实践:虚拟钱包应用,支持用户充值提现、支付等

    public class VirtualWallet {
        // Domain 领域模型 (充血模型)
        private Long id;
        private Long createTime = System.currentTimeMillis();
        private BigDecimal balance = BigDecimal.ZERO;
    ​
        public VirtualWallet(Long preAllocatedId) {
            this.id = preAllocatedId;
        }
    ​
        public BigDecimal balance() {
            return this.balance;
        }
    ​
        public void debit(BigDecimal amount) throws InsufficientBalanceException {
            if (this.balance.compareTo(amount) < 0) {
                throw new InsufficientBalanceException("");
            }
            this.balance.subtract(amount);
        }
    ​
        public void credit(BigDecimal amount) throws InvalidAmountException {
            if (amount.compareTo(BigDecimal.ZERO) < 0) {
                throw new InvalidAmountException("");
            }
            this.balance.add(amount);
        }
    }
    ​
    public class VirtualWalletEntity {
    }
    ​
    public class VirtualWalletRepository {
        public VirtualWalletEntity getWalletEntity(Long walletId) {
            return new VirtualWalletEntity();
        }
    ​
        public BigDecimal getBalance(Long walletId) {
            return new BigDecimal(1);
        }
    ​
        public void updateBalance(Long walletId, BigDecimal balance) {
        }
    }
    ​
    public class VirtualWalletTransactionRepository {
    }
    ​
    public class VirtualWalletService {// 通过构造函数或者 IOC 框架注入
        private VirtualWalletRepository walletRepo;
        private VirtualWalletTransactionRepository transactionRepo;
    ​
        public VirtualWallet getVirtualWallet(Long walletId) {
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            VirtualWallet wallet = convert(walletEntity);
            return wallet;
        }
    ​
        public BigDecimal getBalance(Long walletId) {
            return walletRepo.getBalance(walletId);
        }
            
        // 增加某钱包金额
        public void debit(Long walletId, BigDecimal amount) {
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            VirtualWallet wallet = convert(walletEntity);
            wallet.debit(amount);
            walletRepo.updateBalance(walletId, wallet.balance());
        }
            
        //减少某钱包金额
        public void credit(Long walletId, BigDecimal amount) {
            VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
            VirtualWallet wallet = convert(walletEntity);
            wallet.credit(amount);
            walletRepo.updateBalance(walletId, wallet.balance());
        }
    ​
        public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
        }
    ​
        private VirtualWallet convert(VirtualWalletEntity walletEntity) {
            return new VirtualWallet(1L);
        }
    }
    

创建型

单例模式

  1. 饿汉式

    启动即初始化

    public class Car {
    ​
        private static final Car car = new Car();
    ​
        public static Car getInstance() {
    ​
            return car;
        }
    ​
        private Car() {
    ​
        }
    ​
        public void run() {
            System.out.println("go go go !!!");
        }
    }
    
  2. 懒汉式

    第一次使用时初始化

    public class Car {
    ​
        private static Car car2;
    ​
        /**
         * 线程安全增加 synchronized修饰
         */
        public static synchronized Car getInstance() {
            if (car2 == null) {
                System.out.println("第一次创建对象:买车了。。。");
                car2 = new Car();
            }
    ​
            return car2;
        }
    ​
        private Car() {
    ​
        }
    ​
        public void run(){
            System.out.println("走");
        }
    }
    

工厂模式(Factory)

工厂模式可以分为:简单工厂模式(也叫静态工厂模式)、工厂方法模式、以及抽象工厂模式。

工厂模式是编程中经常用到的一种模式。它的主要优点有:

  • 可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
  • 对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
  • 降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。

总结:

  1. 简单工厂:根据if else 判断需要创建那个对象,直接return =>new 出来的对象
  2. 工厂方法:以定义接口定义实例化对象的方法,根据所要实例化的对象新建不同的工厂实现类,实现定义接口
  3. 抽象工厂:与工厂方法相似,知识抽象工厂中的工厂,只是每一个工厂有多个实例化方法,每个方法分别返回不同的对象

简单工厂模式

比较直接,直接通过传入的参数判断需要实例化的对象

public class GarageFactory {
    public static IVehicle getVehicle(String type) {
        if ("car".equals(type)) {
            return new Car();
        } else if ("motorcycle".equals(type)) {
            return new Motorcycle();
        }
        throw new IllegalArgumentException("请输入车类型");
    }
}


public static void main(String[] args) {
  XiaoMing xiaoMing = new XiaoMing();
  // 小明骑摩托车去学校
  IVehicle motorcycle = GarageFactory.getVehicle("motorcycle");
  xiaoMing.goToSchool(motorcycle);
  // 小明开汽车去旅游
  IVehicle car = GarageFactory.getVehicle("car");
  xiaoMing.travel(car);
}

工厂方法模式

interface IFactory {
  public ICar createCar();
}
class Factory implements IFactory {
  public ICar createCar() {
    Engine engine = new Engine();
    Underpan underpan = new Underpan();
    Wheel wheel = new Wheel();
    ICar car = new Car(underpan, wheel, engine);
    return car;
  }
}
public class Client {
  public static void main(String[] args) {
    IFactory factory = new Factory();
    ICar car = factory.createCar();
    car.show();
  }
}

使用该方法后,调用端的耦合度大大降低了。并且是可以扩展的,以后如果想组装其他的汽车,只需要再增加一个工厂类的实现就可以。无论是灵活性还是稳定性都得到了极大的提高。

抽象工厂模式

public interface IConfigParserFactory {
    IRuleConfigParser createRuleParser();
    ISystemConfigParser createSystemParser();
    //此处可以扩展新的parser类型,比如IBizConfigParser
}

public class XmlConfigParserFactory implements IConfigParserFactory {
    @Override
    public IRuleConfigParser createRuleParser() {
        return new XmlRuleConfigParser();
    }

    @Override
    public ISystemConfigParser createSystemParser() {
        return new XmlSystemConfigParser();
    }
}

public class JsonConfigParserFactory implements IConfigParserFactory {
    @Override
    public IRuleConfigParser createRuleParser() {
        return new JsonRuleConfigParser();
    }

    @Override
    public ISystemConfigParser createSystemParser() {
        return new JsonSystemConfigParser();
    }
}

建造者模式(Builder)

需求:定义一个资源池配置类ResourcePoolConfig

public class ResourcePoolConfig {
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;

    private ResourcePoolConfig(Builder builder) {
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }

    //...省略getter方法...
    //我们将Builder类设计成了ResourcePoolConfig的内部类。
    //我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
    public static class Builder {
        private static final int DEFAULT_MAX_TOTAL = 8;
        private static final int DEFAULT_MAX_IDLE = 8;
        private static final int DEFAULT_MIN_IDLE = 0;
        private String name;
        private int maxTotal = DEFAULT_MAX_TOTAL;
        private int maxIdle = DEFAULT_MAX_IDLE;
        private int minIdle = DEFAULT_MIN_IDLE;

        public ResourcePoolConfig build() {
            // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }
            if (maxIdle > maxTotal) {
                throw new IllegalArgumentException("...");
            }
            if (minIdle > maxTotal || minIdle > maxIdle) {
                throw new IllegalArgumentException("...");
            }
            return new ResourcePoolConfig(this);
        }

        public Builder setName(String name) {
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }
            this.name = name;
            return this;
        }

        public Builder setMaxTotal(int maxTotal) {
            if (maxTotal <= 0) {
                throw new IllegalArgumentException("...");
            }
            this.maxTotal = maxTotal;
            return this;
        }

        public Builder setMaxIdle(int maxIdle) {
            if (maxIdle < 0) {
                throw new IllegalArgumentException("...");
            }
            this.maxIdle = maxIdle;
            return this;
        }

        public Builder setMinIdle(int minIdle) {
            if (minIdle < 0) {
                throw new IllegalArgumentException("...");
            }
            this.minIdle = minIdle;
            return this;
        }

    }
}

public class Test {
    public static void main(String[] args) {
        ResourcePoolConfig config = new ResourcePoolConfig
                .Builder() //内部静态类
                .setName("db connection pool") //设置配置项
                .setMaxTotal(16)
                .setMaxIdle(10)
                .setMinIdle(12)
                .build();	//校验数据
    }
}

原型模式

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

原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝比较节省时间和内存。如果拷贝的对象是不可变的可以使用浅拷贝。

需求:同步数据库中关于搜索关键词的数据到内存中的HashMap中

系统背景:系统B定期更新新版本的关键词数据到数据库中

解决方案:

在系统 A中,记录当前数据的版本 Va 对应的更新时间 Ta,从数据库中捞出更新时间大于 Ta 的所有搜索关键词,也就是找出 Va 版本与最新版本数据的“差集”,然后针对差集中的每个关键词进行处理。如果它已经在散列表中存在了,我们就更新相应的搜索次数、更新时间等信息;如果它在散列表中不存在,我们就将它插入到散列表中;

增加特殊需求:然后又增加了一个特殊需求,需要保证系统A中的关键词数据都必须是同一个版本,要么都是版本a,要么都是版本b。

最终解决方案:

我们把正在使用的数据的版本定义为“服务版本”,当我们要更新内存中的数据的时候,我们并不是直接在服务版本(假设是版本 a 数据)上更新,而是重新创建另一个版本数据(假设是版本 b 数据),等新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b。这样既保证了数据一直可用,又避免了中间状态的存在。

public class Demo {
    private HashMap<String, SearchWord> currentKeywords = new HashMap<>();
    private long lastUpdateTime = -1;

    public void refresh() {
        // Shallow copy
        HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();
        
        // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
                newKeywords.remove(searchWord.getKeyword());
            }
            
            newKeywords.put(searchWord.getKeyword(), searchWord);
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }

    private List<SearchWord> getSearchWords(long lastUpdateTime) {
        // TODO: 从数据库中取出更新时间>lastUpdateTime的数据;
        return null;
    }
}

结构型

代理模式

动态代理:

所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类

Spring Aop 底层原理基于动态代理

/**
 * @Description: 动态代理
 * @Author: yanzhihao
 * @Date: 2022/1/25 1:47 下午
 */
public class MetricsCollectorProxy {
    private MetricsCollector metricsCollector;

    public MetricsCollectorProxy() {
        this.metricsCollector = new MetricsCollector();
    }

    public Object createProxy(Object proxyObject) {
        // 需要代理的类实现的所有接口
        Class<?> [] interfaces = proxyObject.getClass().getInterfaces();
        // 代理执行方法
        DynamicProxyHandler handler = new DynamicProxyHandler(proxyObject);
        // 将类加载器、需要代理的类实现的所有接口、代理执行方法handler 作为参数传入,创建代理对象
        return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(), interfaces, handler);
    }

    private class DynamicProxyHandler implements InvocationHandler {
        private Object proxyObject;

        public DynamicProxyHandler(Object proxyObject) {
            this.proxyObject = proxyObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTimestamp = System.currentTimeMillis();

            // 执行需要代理的方法
            Object result = method.invoke(proxyObject, args);

            // 以上执行完成后,开始执行下面通过动态代理后附加的操作
            long endTimestamp = System.currentTimeMillis();
            long responseTime = endTimestamp - startTimestamp;
            String apiName = proxyObject.getClass().getName() + ":" + method.getName();
            RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp, endTimestamp);
            metricsCollector.recordRequest(requestInfo);
            return result;
        }
    }
}

public class Test {
    public static void main(String[] args) {
        MetricsCollectorProxy proxy = new MetricsCollectorProxy();
        IUserController userController = (IUserController) proxy.createProxy(new UserController());
        userController.getUser();
    }
}

使用步骤:

  1. 新增动态代理自定义handle类用来编写代理逻辑,该类需要实现InvocationHandler重写invoke方法,来实现具体的代理逻辑
  2. 利用Java特性便写生成代理对象的方法,即Proxy.newProxyInstance(...)
  3. 使用代理对象通过2中的方法生成代理对象,调用需要代理的方法,这样就可以在原有方法的基础上增加代理类中invoke方法的逻辑

桥接模式

“将抽象和实现解耦,让它们能独立开发”

/**
* 业务背景:需要发送通知或警告,不同程度的通知或警告的逻辑不通
*/

public interface MsgSender {
    void send(String message);
}

public class EmailMsgSender implements MsgSender{
    private List<String> emilList;

    public EmailMsgSender(List<String> emilList) {
        this.emilList = emilList;
    }

    @Override
    public void send(String message) {
        // ... 邮件通知发送逻辑
    }
}

public class TelephoneMsgSender implements MsgSender{
    private List<String> telephones;

    public TelephoneMsgSender(List<String> telephones) {
        this.telephones = telephones;
    }

    @Override
    public void send(String message) {
        //... 手机信息通知发送逻辑
    }
}

public abstract class Notification {
    protected MsgSender msgSender;

    public Notification(MsgSender msgSender) {
        this.msgSender = msgSender;
    }

    public abstract void notify(String message);
}

public class SevereNotification extends Notification{

    public SevereNotification(MsgSender msgSender) {
        super(msgSender);
    }

    @Override
    public void notify(String message) {
        msgSender.send(message);
    }
}

public class UrgencyNotification extends Notification{

    public UrgencyNotification(MsgSender msgSender) {
        super(msgSender);
    }

    @Override
    public void notify(String message) {
        msgSender.send(message);
    }
}

装饰器模式

参考Java源码BufferedInputStreamInputStreamFilterInputStream三个类之间的关系

其中FilterInputStream类对InputStream类中的方法进行了装饰,避免了代码重复

//示例非源码
public class BufferedInputStream {
    protected volatile InputStream in;

    protected BufferedInputStream(InputStream in) {
        this.in = in;
    }
  	// f()函数不需要增强,只是重新调用一下InputStream in对象的f()
    public void f() {
        in.f();
    }
}

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。 为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

适配器模式

使用适配器模式 封装外部缺陷接口/方法

/**
 * @Description: 外部sdk
 * @Author: yanzhihao
 * @Date: 2022/2/7 5:26 下午
 */
public class CD {
    public void staticFunction1() {}

    public void uglyNamingFunction2() {}

    public void tooManyParamsFunction3(int paramA, int paramB, int... args) {}

    public void lowPerformanceFunction4() {}
}

public class ParamsWrapperDefinition {
    public int getParamA;
    public int getParamB;
    public int getParamC;
}

/**
 * 适配器接口定义,对sdk进行重构的定义
 */
public interface ITarget {
    void function1();
    void function2();
    void function3(ParamsWrapperDefinition paramWrapper);
    void function4();
}

/**
 * 使用适配器模式 封装外部缺陷接口/方法
 * @Description:
 * @Author: yanzhihao
 * @Date: 2022/2/7 5:32 下午
 */
public class CDAdaptor extends CD implements ITarget {

    @Override
    public void function1() {
        super.staticFunction1();
    }

    @Override
    public void function2() {
        super.uglyNamingFunction2();
    }

    @Override
    public void function3(ParamsWrapperDefinition paramWrapper) {
        super.tooManyParamsFunction3(paramWrapper.getParamA, paramWrapper.getParamB, paramWrapper.getParamC);
    }

    @Override
    public void function4() {
        super.lowPerformanceFunction4();
    }
}

统一多个类的接口设计

/**
 * 使用适配器模式
 * 统一的接口定义
 */
public interface ISensitiveWordsFilter {
    String filter(String text);
}

public class ASensitiveWordsFilter {

    public String filterSexyWords(String text) {
        return "";
    }

    public String filterPoliticalWords(String maskedText) {
        return "";
    }
}

public class ASensitiveWordsFilter {

    public String filterSexyWords(String text) {
        return "";
    }

    public String filterPoliticalWords(String maskedText) {
        return "";
    }
}

public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {

    private ASensitiveWordsFilter aFilter;

    @Override
    public String filter(String text) {
        String maskedText = aFilter.filterSexyWords(text);
        maskedText = aFilter.filterPoliticalWords(maskedText);
        return maskedText;
    }
}

public class RiskManagement {

    private List<ISensitiveWordsFilter> filters = new ArrayList<>();

    public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
        filters.add(filter);
    }

    public String filterSensitiveWords(String text) {
        String maskedText = text;
        for (ISensitiveWordsFilter filter : filters) {
            maskedText = filter.filter(maskedText);
        }

        return maskedText;
    }
}

替换依赖的外部系统

public interface IA {

    void fa();
}

public class A implements IA{
    //...
    @Override
    public void fa() {
        //...
    }
}

public interface IB {

    void fb();
}

public class B implements IB{
    @Override
    public void fb() {
        // ...
    }
}

public class BAdaptor implements IA {

    private B b;

    public BAdaptor(B b) {
        this.b = b;
    }

    @Override
    public void fa() {
        // ...
        b.fb();
    }
}

public class Demo {

    private IA a;

    public Demo(IA a) {
        this.a = a;
    }

    //...
}

public class Test {

    public static void main(String[] args) {
        //一开始使用外部A系统
        Demo a = new Demo(new A());

        //将A系统更换为外部B系统
        Demo b = new Demo(new BAdaptor(new B()));
    }
}

兼容老版本接口

public class Emueration {
}

public class Collections {

    public static Emueration emueration(final Collection c) {
        return new Emueration() {
            Iterator i = c.iterator();

            public boolean hasMoreElements() {
                return i.hasNext();
            }

            public Object nextElement() {
                return i.next();
            }
        };
    }
}

适配不同格式的数据

public class Test {

    public static void main(String[] args) {
        List<String> strings = Arrays.asList("1", "2", "3");
    }
}

总结

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。

代理模式: 代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式: 桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式: 装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式: 适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

门面模式

门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。

  1. 解决易用性问题
  2. 解决性能问题
  3. 解决分布式事务问题

组合模式

组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式。

享元模式

所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。具体来讲,当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段),提取出来设计成享元,让这些大量相似对象引用这些享元。

享元模式的代码实现非常简单,主要是通过工厂模式,在工厂类中,通过一个 Map 或者List 来缓存已经创建好的享元对象,以达到复用的目的。

  • 享元模式再Integer和String中都有应用

    • Integer中缓存了 -128~127的值
    • String中缓存了内存中已用字符串

行为型

观察者模式

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

public interface Subject {
    
    void registerObserver(Observer observer);
    
    void removeObserver(Observer observer);
    
    void notifyObservers(Message message);
}

public interface Observer {

    void update(Message message);
}

public class ConcreteSubject implements Subject {

    private List<Observer> observers = new ArrayList<>();

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(Message message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

public class ConcreteObserverOne implements Observer {

    @Override
    public void update(Message message) {
        //TODO: 获取消息通知,执行自己的逻辑...
        System.out.println("ConcreteObserverOne is notified.");
    }
}


public class ConcreteObserverTwo implements Observer {

    @Override
    public void update(Message message) {
        //TODO: 获取消息通知,执行自己的逻辑...
        System.out.println("ConcreteObserverTwo is notified.");
    }
}

/**
 * 观察者模式
 * @author yanzhihao
 * @since 2022/5/7
 */
public class Demo {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        subject.registerObserver(new ConcreteObserverOne());
        subject.registerObserver(new ConcreteObserverTwo());
        subject.notifyObservers(new Message());
    }
}

模板模式

模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。在GoF 的《设计模式》一书中,它是这么定义的:

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

翻译成中文就是:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

/**
 * 模板模式
 * @author yanzhihao
 * @since 2022/5/7
 */
public abstract class AbstractClass {

    public final void templateMethod() {
        //...
        method1();
        //...
        method2();
    }

    protected abstract void method1();
    protected abstract void method2();
}

public class ConcreteClass1 extends AbstractClass {
    @Override
    protected void method1() {
        //...
    }

    @Override
    protected void method2() {
        //...
    }
}

public class ConcreteClass2 extends AbstractClass {
    @Override
    protected void method1() {
        //...
    }

    @Override
    protected void method2() {
        //...
    }
}

public class Demo {
    public static void main(String[] args) {
        AbstractClass demo = new ConcreteClass1();
        demo.templateMethod();
    }
}
  • 复用:InputStream、AbstractList

    • Java IO 类库中,有很多类的设计用到了模板模式,比如 InputStream、OutputStream、Reader、Writer
    • Java AbstractList 类中,addAll() 函数可以看作模板方法
  • 扩展:Servlet、JUnit TestCase

模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。