设计模式-Java版

声明:

本内容主要根据刘韬著《秒懂设计模式》一书,外加网上一些参考文档综合整理记录而来,仅用于个人学习,不用做任何商业用途。

0 原则

  • 面向对象软件设计遵循的5大原则:S.O.L.I.D

  • 单一职责:类的角色职责设计应该单一,方法完成的功能单一

  • 开闭:对扩展开放,对修改关闭,即不要修改已有代码,而要去编写新的代码;修改的代价是巨大的

  • 里氏替换:里氏是提出该原则的作者的姓,替换指父类与子类的可替换性,即在任何父类出现的地方,子类也一定可以出现并替换,继承与多态为此而生,设计时面向接口编程而不是深入到子类中去

  • 接口隔离:对高层接口的独立、分化,对类的依赖基于最小接口,而不依赖不需要的接口;如设计动物类且包含移动和发声动作,但对于某些动物来说不一定非要发声,如此产生了多余的空方法实现;高层接口可设计为可移动的与可发声的两个接口,即接口相互隔离细化,自由组装

  • 依赖导致:高层类不直接依赖需要调用的类,而是依赖其上层抽象;如A类需要调用B类,为B类抽象一个父接口,A类引用父接口,如此间接访问B类,无论B类怎样变化都与A类无关,降低耦合

  • 迪米特法则(附加):也叫最少知识原则,它提出一个模块对其他模块应该知之甚少,甚至彼此陌生,以最小化、简单化模块间的通信,达到松耦合

1 单例-Singleton

public class User {
    private static final User user = new User();
        // private:保证实例的**私有性、不可见性和不可访问性**
        // static:确保**静态性**,将实例放入内存静态区,在类加载时初始化且永生,不被GC
        // final:确保实例为**常量**,一旦赋值不被修改
        // new:初始化实例并赋予静态实例user
    private User(); // 构造器私有化
    public static User getInstance() { 
        return user;
    }
    // User.getInstance():提供外部唯一合法的访问
    // 以上为饿汉模式,可能造成空间浪费即没有被获取时白白实列化
    
    // 改进:懒汉式,延迟实例化
    private static User user; // 去掉final
    public static User getInstance() {
        if (null == user) {
            user = new User(); // 相较于饿汉式的缺点是第一次请求时速度慢
        }
        return user;
    }
    // 致命缺点:在多线程模式下并发请求,执行if为空判断时同时成立,则会多次实例化并进行多次赋值覆盖;改进:加上同步锁排队执行:
    public static synchronized User getInstance() { ... }
    // 同步在方法上,代价过程沉重
    public static User getInstance() {
        if (null == user) {
            synchorinized(Sun.class) {
                if (null == user) {
                    user = new User();
                }
            }
        }
    }
    // 1、细化了同步区域,使得阻塞时间减少;2、将阻塞地控制在if判断的地方,控制观察者在排队的时候同时观察;3、双检索机制,外层放宽入口,保证线程并发的高效性,内层加锁同步,保证实例化的单次运行
    // 反正迟早需要实例化,加锁解锁和同步的意义不大,所以常用饿汉式
}

2 原型-Prototype

  • 制造业中,大批量生产之前,先研发概念模型并基于各种参数指标进行检验,符合要求则参照模型批量生产

  • 原型模式以达到原型实例创建副本实例即可,无需知道原始类,即用对象创建对象而非类创建对象-克隆;适用于初始化非常复杂,或者对象创建需要耗费大量资源的情形

    List<User> users = new ArrayList();
    for (int i = 0; i < 100; i++) {
        User user = new User(...); // 不仅批量生产,而且构造器中逻辑复杂;这是非常耗时的生产过程
        users.add(user);
    }
    // 改进:实现克隆接口Cloneable接口,让外部能对实体类进行克隆操作
    public class User implements Cloneable {
        // ...
        @Override
        public User clone() throws CloneNotSupportException {
            return (User) super.clone();
        }
    }
    // 抽象出克隆工厂类
    public class UserFactory {
        private static User user = new User(); // 原型
        public static User getInstance() {
            User clone = user.clone();
            // clone.XXX(); 个性化操作
            return clone;
        }
    }
    // 浅拷贝:如果User中包含引用类型属性如People类,则克隆User时,只会复制User中的原始类型如int的数据,people也被复制但复制的是其地址引用/指针,于是副本与原型拥有的peopeo指向同一个而不是各自拥有的
    // 深拷贝:
        public User clone() throws CloneNotSupportException {
            User clone = super.clone();
            clone.setPeople(this.people.clone());
            return clone;
        }  
  • 原型模式的本质:原型接口Prototype:Cloneable;及接口实现:调用super.clone();客户端调用方法以克隆对象;克隆操作是JVM对内存的操作,数据流的转换,免去了类加载、实例化、初始化等new操作

3 工厂方法-FactoryMethod

  • 前置条件:一个父(抽象)类Father,N个子(实现)类Son;当需要实例化这N个子类时,传统写法是N段重复的Father user = new Son();

  • 简单工厂类:将这些实例化封装在工厂类中并提供工厂方法去实例化类,实例化该工厂类并调用工厂方法,以参数形式指定想要实例化最终想要的子类;缺点:扩展性差,工厂类需要反复修改,即当子类越来越多,case越来越多且需指定更多的入参

    public class SimpleFactory {
        // ... 属性
        public Father create(String type, ...) {
            Father user = null;
            switch(type) {
                case "...":
                    user = new Son1(...);
                    break;
                case "...":
                    user = new Son2(...);
                    break;
                // ...
            }
            return user;
        }
    }
  • 改善简单工厂:制定工业制造标准,即将工厂抽象为工厂接口以确立统一的工业制作标准

    public interface Factory {
        Father create(...);
    }
    public class Son1Factory implements Factory {
        @Override
        public Father create(...) {
            return new Son1(...);
        }
    }
    // Son2同理;即按子类的不同,拆分为多个实现类;如此实现多态化的工厂,各个工厂有自己的生产策略,保证了系统的解耦、兼容性和扩展性
    // 总结工厂方法模式的角色分类:Father业务顶级父类/接口,Son多个子类,Fatory工厂接口,SonFactory工厂实现
    // 缺点:各个工厂实现只定义了一个工厂方法,不足以应付复杂业务情形;且工厂实现会越来越多

4 抽象工厂-AbstractFactory

  • 区别于工厂方法的抽象制造方法,抽象工厂还对工厂进行抽象,使用横向(方法)和纵向(属性)的双重划分;改善工厂方法的缺点;具体体现在对公共属性的抽象,子类去初始化自己的属性

    public abstract class Common {
        int id;
        String name;
        // ...
        public Common(int id, String name) {
            this.id = id;
            this.name = name;
        }
        public void show();
        // ...
    }
    // 各种继承,各个继承者有自己的属性初始值设置
    public abstract class GoodCommon extends Common {
        public GoodCommon(int id, String name) {
            super(1, "goodCommon");
        }
    }
    public abstract class BadCommon extends Common {
        public BadCommon(int id, String name) {
            super(2, "badCommon");
        }
    }
    // ...
    ​
    // 对各个继承者的实现
    public class GoodHuman implements GoodCommon {
        public GoodHuman(int id, String name) {
            super(id, name); // GoodHuman的制造标准
        }
        @Override
        public void show() { ... }
    }
    public class BadHuman implements BadCommon {
        public BadHuman(int id, String name) {
            super(id, name); // BadHuman的制造标准
        }
        @Override
        public void show() { ... }
    }
    public class GoodAnimal implements GoodCommon {
        public GoodAnimal(int id, String name) {
            super(id, name); // GoodAnimal的制造标准
        }
        @Override
        public void show() { ... }
    }
    public class BadAnimal implements BadCommon {
        public BadAnimal(int id, String name) {
            super(id, name); // BadAnimal的制造标准
        }
        @Override
        public void show() { ... }
    }
    ​
    // 抽象工厂定义:不只规定一种工厂的实现标准,而是可以包含所有继承者的制造
    public interface FatherFactory {
        GoodCommon createGood();
        BadCommon cerateBad();
    }
    // 不同种类工厂实现须具备初始化所有继承者的能力
    public class HumanFactory implements FatherFacotry {
        // ... 用于构造器的属性
        @Override
        public GoodCommon createGood() {
            GoodCommon goodHuman = new GoodHuman(...);
        }
        @Override
        public BadCommon cerateBad() {
            BadCommon badHuman = new BandHuman(...);
        }
    }
    public class AnimalFactory implements FatherFatory {
        // ... 用于构造器的属性
        @Override
        public GoodCommon createGood() {
            GoodCommon goodAnimal = new GoodAnimal(...);
        }
        @Override
        public BadCommon cerateBad() {
            BadCommon badAnimal = new BandAnimal(...);
        }
    }
    // 使用
    main {
        FatherFactory factory = new HumanFactory(); // 大类1
        Common goodHuman = factory.createGood();
        goodHuman.show();
        Common badHuman = factory.createBad();
        
        fatory = new AnimalFactory(); // 大类2
        Common goodAnimal = factory.cteateGood();
        Common badAnimal = factory.cteateBad();
    }
    // 总结抽象工厂的角色:顶层抽象产品Common及抽象产品分类GoogCommon、BadCommon;对各分类产品的实现GoodHuman、BadHuman和GoodAnimal、BadAnimal;工厂接口FatherFactory;工厂接口的实现HumanFactory、AnimalFactory;扩展时新扩展抽象产品分类,并添加在工厂接口以规范要生产它即可

5 建造者-Builder

  • 又称生成器模式,适用于构建会包含多个对象组件的复杂对象,即组装各个小对象为一个大对象,甚至小对象之间可能存在依赖性,组装具有顺序性

    // 建筑物
    public class Building {
        private List<String> buildingComponents = new ArrayList<>(); // 模拟存储建筑物所需的所有对象
        public void setBasement(String basement) {
            this.buildingComponents.add(basement); // 地基
        }
        public void setWall(String wall) {
            this.buildingComponents.add(wall); // 墙体
        }
        public void setRoof(String roof) {
            this.buildingComponents.add(roof); // 屋顶
        }
    }
    // 建筑施工方
    public interface Builder {
        public void buildBasement(); // 建造地基
        public void buildWall(); // 建造墙体
        public void buildRoof(); // 建造屋顶
        public Building getBuilding(); // 返回建筑物
    }
    // 房屋建筑施工方
    public class HouseBuilder implements Builder {
        private Building house;
        public HouseBuilder() {
            house = new Building();
        }
        @Override
        // ...
        @Override
        public Building getBuilding() {
            return this.house;
        }
    } 
    // 别墅建筑施工方
    public class ApartmentBuilder implements Builder {
        // 同理
    }
    ​
    // 工程总监:监督各个小对象的建造流程
    public class Director {
        private Builder builder;
        public Director(Builder builder) {
            this.builder = builder;
        }
        public Building direct() {
            this.builder.buildBasement();
            this.builder.buildWall();
            this.builder.buildRoof();
            return builder.getBuilding()
        }
        // getter(),setter()
    }
    // 使用
    main() {
        Director director = new Director(new HouseBuilder());
        director.direct();
        director.setBuilder(new ApartmentBuilder());
        director.direct();
    }
    // 总结:建造者模式的角色:产品Building,建造者接口Builder及实现类,持有建造者接口引用的指导者Director

6 门面-Facade

  • 也称外观模式,即与外界接触的临界面或接口,对各个复杂的子系统的封装以隐藏操作细节,达到高内聚低耦合

  • 实例:原先在main()中实例化各种子系统对象并使用对象,可将这些对象抽象到一个类中作为属性,提供对这些属性操作的方法,在main()中调用该方法即可;典型:Web开发中,Service层封装了诸多的子系统,如对Dao层的调用、事务处理等,最终提供一个方法如update()共Controller层直接一步调用

7 组合-Composite

  • 叉树结构:对象的整体,与对象包含的子对象具有相同或相似的数据结构;如二叉树,每个根元素都包含两个子元素,子元素也包含两个子子元素;又如字-词-句-段落-文章-书,又如文件目录结构;具有相似结构则完全没必要为每一个元素都定义不同的类,而是使用组合模式来表达整体与部分的层次结构,抽象公共部分,特殊化不同部分

  • 文件目录结构:根节点以盘符开始,其下分为文件夹(枝节点)和文件(叶子节点),如此递归

    public abstract class Node {
        protected String name; // 节点名
        public Node(String name) {
            this.name = name;
        }
        // 添加子节点,由子类各自实现
        protected abstract void addChildrenNode(Node childNode);
        // 其他方法,如删除节点,获取节点
        // 树形展示:传入空格参数,一个层级一个空格
        protected void treeShow(int space) {
            for (int i = 0; i < space; i++) {
                System.out.println(" "); // 先循环输出空格
            }
            System.out.println(this.name); // 最后输出名字
        }
        protected void treeShow() {
            this.treeShow(0); // 默认从0列开始展示
        }
    }
    // 枝节点
    public class Folder extends Node {
        private List<Node> childNodes = new ArrayList<>(); // 存储子文件夹和子文件
        public Folder(String name) {
            super(name);
        }
        @Override
        protected void addChildrenNode(Node childNode) {
            childNodes.add(childNode);
        }
        @Override
        protected void treeShow(int space) {
            super.treeShow(space); // 先展示当前节点名称
            space++;
            for (Node node: childNodes) {
                node.treeShow(space); // 展示当前节点的子节点;因为子节点有自己的展示逻辑,所以不必关心当前节点是文件夹还是文件
            }
        }
    }
    // 叶子节点
    public class File extends Node {
        public File(String name) {
            super(name);
        }
        @Override
        protected void addChildrenNode(Node childNode) {
            "不能添加子节点"; // 递归终结
        }
        @Override
        protected void treeShow(int space) {
            super.treeShow(space); // 展示逻辑的递归终结
        }
    }
    // 使用:构建目录树
    main() {
        Node d = new Folder("D:"); // D盘
        Node myFile = new Folder("我的文档"); // 文件夹
        myFile.add(new Folder("秘密文档")); // 子文件夹
        myFile.add(new File("xxx.txt")); // 子文件
        d.add(myFile);
    }
    // 总结:组合模式的角色:组件接口(Node),复合组件即实现类(Folder)即根\枝节点,终端组件(File)即叶子节点

8 装饰器-Decorator

  • 在运行时动态的为原始对象增加一些额外的功能,使其变得更加强大;区别于继承,继承是在编译时静态地通过对原始类的继承完成

  • JDK中典型装饰器模式应用:java.io中的一系列流处理类InputStream,FileInputStream...等,如对压缩文件的解压操作,就是用构造器嵌套结构进行文件流装饰(俄罗斯套娃):ZipInputStream zipInputStream = new ZipInputStream(new BufferredInputStream(new FileInputStream(new File("xxx.zip"))));其中文件file被构造为文件输入流FileInputStream(这不是装饰),用缓冲流BufferredInputStream装饰使其具有内存缓冲功能,用ZipInputStream装饰使其具备Zip格式文件功能

    // 可展示者
    public interface Showable {
        void show(); // 规范展示功能
    }
    public class MyShOW implements Showable {
        @Override
        public void show() {
            // ...
        }
    }
    ​
    // 装饰器:对展示功能的装饰
    public class Decorator implements Showable {
        Showable showable; // 被装饰者,如此时传入MyShow,就对MyShow做装饰
        public Decorator(Showable showable) {
            this.showable = showable;
        }
        @Override
        public void show() {
            // ...前置装饰
            showable.show();
            // ...后置装饰
        }
    }
    // 使用
    main() {
        new Decorator(new MyShow()).show();
    }
    // 添加其他装饰效果,错误设计是编写多个MyShow,在装饰器中依赖和引用它们;因为不是所有的展示都需要相同的装饰;单个装饰器应该只负责独有的装饰功能,多个装饰功能由展示者自由搭配;另一个糟糕设计是编写多个实现Showaable的Decorator类,因为这样所有的Decorator都会依赖和引用被装饰者Showable,造成代码冗余;所以需要将装饰器抽象化
    public abstract class Decorator implements Showable {
        protected Showable showable;
        public Decorator(Showable showable) {
            this.showable = showable;
        }
        @Override
        public void show() {
            showable.show(); // 装饰由实现类去实现
        }
    }
    public class Decorator1/2/3... extends Decorator {
        public Decorator1/2/3...(Showable showable) {
            super(showable);
        }
        @Override
        public void show() {
            // ...前置装饰
            showable.show();
            // ...后置装饰
        }
    }
    // 使用
    main() {
        Showable showable = new Decorator1/2/3...(new Decorator1/2/3...(new MyShow())); // 不同的顺序和嵌套个数体现不同的装饰效果
        showable.show();
    }
    // 总结:装饰器设计模式的角色:包含被装饰者和装饰器对应的接口标准的组件接口(Showable),被装饰者的组件接口实现(MyShow),装饰器(Decorator)及装饰器实现Decorator1/2/3...

9 适配器-Adapter

  • 也称转换器模式,用于兼容两方接口

    // 三相插孔与两相查插孔
    public interface TriplePin {
        public void electrify(int l, int n, int e); // 火线、零线、地线
    }
    public interface DualPin {
        public void electrify(int l, int n);
    }
    // 使用两相插孔的TV;很明显此TV不能使用三相插孔,TriplePin tv = new TV(1, 2);报错类型不匹配
    public class TV implements DualPin {
        @Override
        public void electrify (int l, int n) {
            // ...
        }
    }
    // 创建适配器,兼容两相和三相插孔
    public class Adapter implements TriplePin {
        private DualPin dualPin;
        // 三相接入两相
        public Adapter(DualPin dualPin) {
            this.dualPin = dualPin;
        }
        // 适配器实现引入目标接口
        @Override
        public void electrify(int l, int n, int e) {
            this.dualPin.electrify(l, n); // 忽略差异e,三相转两相
        }
    }
    // 使用
    main() {
        DualPin dualPin = new TV(); // 两相TV
        TriplePin tripPin = new Adapter(dualPin); // 适配器适配左右两边
        tripPin.electrify(1, 2, -1);
    }
    // 以上适配器属于对象(三相和两项)的通用适配,它可以不顾虑具体是TV还是其他
    // 另一种是扩展性差但简单的专属类适配器,通过同时继承具体类和实现接口实现,如TV适配器
    public class TVAdapter extends TV implements TriplePin {
        @Override
        pubic void electrify(int l, int n, int e) {
            super.electrify(l, n); // 实现接口的方法,调用父类的方法
        }
    }
    // 总结:适配器模式的角色:目标接口(TriplePin),适配器(Adapter),被适配者(DualPin)
  • 适配器模式的重用:让兼容性问题在不修改代码的情况下得以解决;修改代码往往意味着引入重构和测试工作,带来不可预知的风险

10 享元-Flyweight

  • 计算机世界本质是0和1两个"元"的组合;享元即共享元件,使得程序更轻量化;当对象较多且对象有相同内部状态时,用享元模式共享元件对象,避免对象泛滥

11 代理-Proxy

  • 业务对象通过代理把自己的业务托管,供客户端在不方便直接访问业务对象的时候,间接通过代理对象来访问

  • 基本理念:拦截被代理类的原始业务并在其之前或后加入额外的业务逻辑或管控,最终实现不改变原始类的情况下的加工和管理

    // 模拟互联网访问
    public interface Internet {
        void httpAccess(String url); // 连接网络,访问URL
    }
    // 调制解调器-猫实现访问网络
    public class Modem implements Internet {
        public Medem(String password) {
            if (!"123456".equals(password)) {
                throw new Exception("拨号失败,请重试!");
            }
            System.out.println("拨号上网,连接成功!");
        }
        @Override
        public void httpAccess(String url) {
            // ...
        }
    }
    
    // 使用代理机制(而不是由猫去做)完成一些联网时的控制,如URL的安全性校验:路由器代理
    public class RouterProxy implements Internet {
        private Internet internet; // 被代理对象
        private List<String> blacks = Arrayls.asList("...", "...", "..."); // 黑名单
        public RouterProxy() {
            this.internet = new Modem("123456"); // 静态代理;实例化被代理类;区别于装饰器模式的外部注入,前者强调对访问的管控,对客户隐藏实际业务,后者注重对对象的功能增加及灵活的组件搭配
        }
        // 控制和转发
        @Override
        pubic void httpAccess(String url) {
            for (String key: blacks) {
                if (url.contains(key)) {
                    return; // 直接返回,禁止联网
                }
            }
            this.internet.httpAccess(url); // 转发
        }
    }
    // 使用:客户端只需要知道路由器,完全不用管猫是什么
    main() {
        Internet proxy = new RouterProxy();
        proxy.httpAccess("...");
    }
  • 动态代理:被代理者的实例化动态完成

    // 新增了局域网的文件访问
    public interface Internet {
        public void fileAccess(String filePath); // 通过文件路径访问
    }
    // 如果再编写交换机代理SwitchProxy,构造实例化被代理的局域网,并且和路由器代理有相同的代理逻辑,则明显代码冗余
    // 抽象通用的代理逻辑为独立于系统业务的过滤器:实现反射中的动态调用处理器接口以使得过滤器可以代理任意类的任意方法
    public class BlacksFilter implements InvocationHandler {
        private List<String> blacks = Arrays.AsList("...", "...", "....");
        private Object origin; // 被代理对象
        public BlacksFilter(Object origin) {
            this.origin = origin; // 外部动态注入被代理对象
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String arg = args[0].toString(); // 规定目标URL放于参数第一个
            for (String key: blacks) {
                if (arg.contains(key) {
                    return null; // 禁止访问
                }
            }
            return method.invoke(origin, arg); // 反射调用origin对象的method()(被代理类的方法对象)
        }
    }
    // 使用
    main() {
        // 访问外网,使用路由器代理;访问内网,使用交换机代理即SwitchProxy;参数传入过滤器和被代理对象,由此动态组成代理对象
        Internet internet = (Internet) Proxy.newProxyInstance(
        		RouterProxy.class.getClassLoader(),
            	RouterProxy.class.getInterfaces(),
            	new BlacksFilter(new RouterProxy()));
        internet.httpAccess("...");
    }
  • 动态代理典型应用:Spring的AOP:定义切面类@Aspect(等同上面的过滤器):

    • 声明切入点@Pointcut即标记被代理的对象及其方法

    • 声明增强的逻辑即切入的代码块(如上面的黑名单过滤功能),可设置前置执行@Before,后置执行@After,异常处理@AfterThrowing等

    • 框架自动生成代理并切入目标

  • 总结:代理模式的角色:业务接口(Internet),实现业务接口的被代理业务(Modem),实现业务接口且引入被代理对象的代理(RoutProxy,SwitchProxy)

12 桥接-Bridge

  • 将抽象与实现分离,使二者各自单独变化而不受对方的约束,使用时再将它们组合起来

13 模板方法-TemplateMehtod

  • 模板是对多种事物的结构、形式、行为的模式化总结;而模板方法则是对类行为-方法的模式化,使行为规律固化在基类中;对未固化的方法进行抽象,使其得以延续和多态

    // 鲸类:拥有move和eat动作,它的生存live需要调用这两个动作
    public class Whale {
        public void move() { ... }
        public void eat() { ... }
        public void live() {
            move();
            eat();
        }
    }
    // 再定义Human类及其他哺乳类,它和鲸类有一样动作(具体实现不一样),生存也需要调用这些动作
    // 于是共有的行为规律live可以抽象到基类中作为模板方法
    // 抽象哺乳类
    public abstract class Mammal {
        public abstract void move(); // 一般都由子类自己实现各自的move;如果子类的move都一样,基类中可直接实现,子类直接继承;灵活变通,做到虚实结合
        public abstract void eat();
        // 模板方法;final限制方法不能被修改重写
        public final void live() {
            move();
            eat();
        }
    }
    public class Whale {
        @Override
        public void move() { ... }
        @Override
        public void eat() { ... }
    }
    // 使用
    main() {
        Mammal mammal = new Whale();
        mammal.live();
        mammal = new Human();
        mammal.live();
    }
    
    // 模板方法实现采用瀑布模型的项目工作流程(流程一般都是固定的):项目管理
    public abstract class PM {
        public abstract String analyze(); // 需求分析
        public abstract String design(String project); // 软件设计
        public abstract String develop(String projcet); // 代码开发
        public abstract boolean test(String project); // 软件测试
        public abstract void release(String project); // 上线发布
        
        // 一个完整的瀑布模型项目周期
        public final void kickoff() {
            String requirement = analyze();
            String designCode = design(requirement);
            do {
                designCode = develop(designCode);
            } while (!test(designCode)); // 测试失败需要回归代码开发
            release(designCode);
        }
    }
    public class HRProject extends PM {
        @Override
        ......
    }
    // 总结:模板方法模式的角色:包含不可被重写的模板方法的抽象基类,实现类

14 迭代器-Iterator

  • 程序中迭代指对集合中各元素的逐个取用行为;迭代器模式提供一种按顺序访问元素而不关系集合类内部构造-数据结构的机制;用标准迭代器提供统一、通用的迭代方法

    // 重复的翻页是迭代
    public class Book {
        class Page {
            private int index; // 页码
            public Page(int index) {
                this.index = index;
            }
        }
        
        pubic List<Page> pages = new ArrayList<>();
        public Book(int pageSize) {
            for (int i = 0; i < pageSize; i++) {
                this.pages.add(new Page(i + 1)); // 构造一本书的所有页码
            }
        }
    
        public void read() {
            // 非迭代遍历全书
            this.pages.get(0);
            this.pages.get(1);
            // ... 模拟翻页阅读;此种遍历除了页码不同,其他全部相同
    
            // 使用迭代遍历
            for (Page page: pages) {
                page...; // 不必关西页码总数等Page内部有什么属性
            }
        }
    }
    // foreach本质是获取了集合的迭代器来遍历
    // for循环能对List集合遍历,因为其维护了index索引,也就不能遍历没有索引的Set集合,而是需要foreach
    // Collection接口提供通用的迭代标准用于统一遍历方式:iterator()获取迭代器;Map通过EntrySet获取迭代器
    // 自定义迭代器
    // 模拟行车记录仪的存储视频记录:当记录存满时,下一条记录覆盖最老记录
    pubic class DriverRecorder {
        private int index = -1; // 当前位置索引
        private String[] records = new String[10]; // 容量10
        // 增加记录
        public void append(String record) {
            if (index == 9) {
                index = 0; // 从头覆盖
            } else {
                index++;
            }
            records[index] = record;
        }
        // 展示
        public void display() {
            for (int i = 0; i < 10; i++) {
                this.records[i];
            }
        }
    }
    // 使用
    main() {
        DriverRecorder driverRecorder = new DriverRecorder();
        for (int i = 0; i < 20; i++) {
            driverRecorder.append("recorder_" + i);
        }
        driverRecorder.display();
    }
    // 存在问题:当需要使用某一条记录时,对该记录的获取和操作,会破坏DriverRecorder的数据逻辑封装,而其内部结构对用户而言应该透明,如用户没必要知道和维护索引信息,甚至数据的增减也可能导致索引错乱,数据混乱,从而破坏数据安全性
    // 定义和实现迭代器;或者实现util中自带的迭代器接口
    public interface Iteraotr<E> {
        E next(); // 下一个元素
        boolean hasNext(); // 是否有下一个元素
        // 其他方法,如romove()移除元素
    }
    public class DriverRecorder implements Iterable<String> {
        private int index = -1; // 当前位置索引
        private String[] records = new String[10]; // 容量10
        // 增加记录
        public void append(String record) {
            if (index == 9) {
                index = 0; // 从头覆盖
            } else {
                index++;
            }
            records[index] = record;
        }
        // 获取迭代器
        @Override
        public Iterator<String> iterator() {
            return new Itr();
        }
        
        // 迭代器与集合分离,还能访问集合
        private class Itr implements Iterator<String> {
            int cursor = index; // 迭代器游标,迭代器自己维护,不波及集合索引
            int loopCount = 0;
            
            @Override
            public String next() {
                int i = cursor;
                if (cursor == 0) {
                    cursor = 9;
                } else {
                    cursor--;
                }
                loopCount++;
                return records[i];
            }
            
            @Override
            public boolean hasNext() {
                return loopCount < 10;
            }
        };
    }
    // 使用
    main() {
        DriverRecorder driverRecorder = new DriverRecorder();
        for (int i = 0; i < 20; i++) {
            driverRecorder.append("recorder_" + i);
        }
        Iterator<String> iterator = driverRecorder.iterator();
        while (iterator.hasNext()) {
            String record = iterator.next();
            ... // 可以任意操作取出的record
        }
    }

15 责任链-ChainOfResponsibility

  • 责任链是由多个责任节点串联而成的任务链,每个节点是一个业务处理任务;责任链模式允许请求将整个责任链视为一个整体并发送请求,而不必关系链内具体节点业务逻辑和流程走向

    // 审批流模拟:公司报销金额需要经过三个审批角色,分别是财务专员的最高1百审批权限、财务经理的最高2百、财务总监的最高3百审批权限;超过各自审批权限的交由上级审批;超过最终3百的审批不通过
    // 财务专员、经理、总监类
    public class Staff/Manage/CFO {
        private String name;
        public Staff/Manager/CFO(String name) {
            this.name = name;
        }
        // 审批
        public boolean approve(int amount) {
            if (amount < 100/200/300) {
                "财务专员/经理/总监" + name + "审批通过";
                return true;
            } else {
                name + "无权审批,交由上级"; // 总监此处逻辑是:"审批不通过,驳回"
            }
            return false;
        }
    }
    // 使用
    main() {
        int amount = 1000;
        Staff staff = new Staff("专员");
        if (!staff.approve(amount)) {
            Manager manager = new Manager("经理");
            if (!manager.approve(amount)) {
                CFO cfo = new CFO("总监");
                cfo.approve(amount);
            }
        }
    }
    // 办事效率极低,即这些代码毫无设计性,后续维护和扩展成本极高
    // 工作流架构设计:审批业务之间有环环相扣的关联,具有传递性,因此可构造链式工作流
    // 审批人
    public abstract class Approver {
        protected String name;
        protected Approver nextApprover; // 核心:当前审批者的下一位审批人
        public Approver(String name) {
            this.name = name;
        }
        // 设置该引用,串联起责任链
        protected Approver setNextApprover(Approver nextApprover) {
            this.nextApprover = nextApprover;
            return this.nextApprover;
        }
        
        public abstract void approve(int amount);
    }
    // 各自实现
    public class Staff/Manager/CFO extends Approver {
        public Staff/Manager/CFO(String name) {
            super(name); // 调用父构造器
        }
        
        @Override
        public void approve(int amount) {
            if (amount < 100/200/300) {
                "财务专员/经理/总监" + name + "审批通过";
                return;
            } else {
                name + "无权审批,交由上级"; // 总监此处逻辑是:"审批不通过,驳回"
                this.nextApprover.approve(amount); // 调用引用的下一个节点的相同方法,即超过自己业务范围的,传递业务到责任链的下一个节点;总监此处没有这个逻辑
            }
        }
    }
    // 使用:责任链需要知道第一个节点,用于启动整条链
    main() {
        Approver approver = new Staff("专员");
        approver.setNextApprover(new Manager("经理")).setNextApprover(new CFO("总监")); // 构造链式
        approver.approve(1000);
    }
    // 总结:责任链模式的角色:所有业务处理节点的顶层抽象即业务处理者(Approver),链上节点即业务处理实现(Staff/Manager/CFO);复杂的业务还可以构造树形结构即从一个节点分出多个链式

16 策略-Strategy

  • 为达成某个目的而预先策划好方案;但计划往往不如变化快,目标突变或方案无法实施时,需要临时变更方法;策略模式即针对方案的灵活切换;如当一个类的多个方法具有相似行为时,可将它们抽象为多个策略类,在运行时灵活接入以应对不同场景

    // 极简计算器类
    public class Calculator {
        public int add(int a, int b) {
            return a + b;
        }
        pubic int sub(int a, int b) {
            return a - b;
        }
    }
    // 当需要乘法、除法、小数的运算等其他功能时,不得不向Calculator中添加代码;反复的针对Calculator类的修改,使其越来月臃肿且难以维护
    // 抽象:将运算/算法(观察发现它们的公共点就是数字的运算)抽象成独立于计算器的接口,当需要何种算法的时候引入对应的接口即可,即计算器类-目标只需要随心所欲的引用算法类-策略
    public interface Strategy {
        int calculate(int a, int b); // 小数操作定义为另一个策略接口
    }
    public class Addition implements Strategy {
        @Override
        public int calculate(int a, int b) {
            return a + b;
        }
    }
    // 同理,实现减法Substraction策略及其他策略
    // 引用:计算器不存在具体的运算实现,转而引用各种实现策略,具体引用何种策略,在运行时由setter()方法来注入
    public class Calculator {
        private Strategy strategy;
        public void setStrategy(Strategy strategy) {
            this.strategy = strategy;
        }
        public int getResult(int a, int b) {
            return this.strategy.calculate(a, b);
        }
    }
    // 使用
    main() {
        Calculator calculator = new Calculator();
        calculator.setStrategy(new Addition());
        calculator.getResult(1, 2);
    }
    
    // 另一个实例:USB接口
    public interface USB {
        void read(); // 读取数据
    }
    public class KeyBoard/Mouse/Camera implements USB {
        @Override
        public void read() {
            "键盘/鼠标/摄像头...";
        }
    }
    public class Computer {
        private USB usb
        public void setUSB(USB usb) {
            this.usb = usb;
        }
        public void compute() {
            this.usb.read();
        }
    }
    // 使用:不同数据源接口即插即用
    // 总结:对策略抽象、拆分,再拼接和引用,使系统行为可塑性极强;策略模式让策略与系统环境解耦分离,让系统应变能力提升以适应随时变化的需求;角色有:策略接口(Strategy),策略实现(KeyBoard等),系统环境(Calculator等)

17 状态-State

  • 类封装了属性和方法,被实例化后的对象属性体现为某种状态,以至调用其方法时展现出某种相应的行为;如温度变化导致水在固态、液态、气态之间变化,同时产生各种行为如滑动、流动、漂浮等;简单的二元态实例:如开与关

  • 状态模式架构出一套完备的事物内部状态转换机制,并将内部状态包裹起来,对外不可见,使其行为能随其状态的改变而改变,简化了事物的复杂的状态变化逻辑;适用于系统可能会堆积大量的状态判断的场景

    // 模拟简单二元态
    public class Switcher {
        private boolean state = false; // false为关,true为开
        public void switchOn() {
            this.state = true;
        }
        public void swithcOff() {
        	this.state = false;
        }
    }
    // 存在的问题:可以连续调用switchOn()或switchOff();这虽然在代码上没有逻辑错误,但增加了无意义的冗余操作;于是增加操作之前的状态判断
    if (this.state == false) {
        this.state == true;
    } else {
        "已经是关闭了,无需重复操作"; // 另一个方法同理
    }
    // 更复杂的状态转换逻辑:交通灯颜色状态,如:红灯只能切换为黄灯,黄灯可以切换为红灯或绿灯,绿灯只能切换为黄灯;如果再加入操作前的状态判断,则类中会堆积大量逻辑判断代码,体现在代码中则是大量的if...else...equals()等语句
    // 抽象状态接口
    public interface State {
        void switchToGreen(TrafficLight trafficLight); // 通行
        void switchToYellow(TrafficLight trafficLight); // 警示
        void switchToRed(TrafficLight trafficLight); // 禁行
    }
    public class Red implements State {
        @Override
        void switchToGreen(TrafficLight trafficLight) {
            "Error,红灯不能切换为绿灯。"
        }
        @Override
        void switchToYellow(TrafficLight trafficLight) {
            trafficLight.setState(new Yellow());
            "红灯切换为黄灯。"
        }
        @Override
        void switchToRed(TrafficLight trafficLight) {
            "Error,已经是红灯了,不能自己切换为自己。"
        }
    }
    // 类Yellow,Green同理,实现方法重写且有自己的状态修改逻辑;如此,将“状态”接口化,从TrafficLight中抽离出来,摆脱复杂的逻辑判断
    // 核心为入参TrafficLight交通灯,使用TrafficLight的setState()来设置,避免了if...else的逻辑判断;系统环境不再处理任何状态响应和切换逻辑,而是转发给当前状态对象去处理,同时将自身引用传递下去,即系统环境只需要持有当前状态,而不必关心如何根据状态来响应
    public class TrafficLight {
        State state = new Red(); // 交通灯的默认初始状态
        public void setState(State state) {
            this.state = state;
        }
        public void switchToGreen() {
            this.state.switchToGreen(this);
        }
        public void switchToYellow() {
            this.state.switchToYellow(this);
        }
        public void switchToRed() {
            this.state.switchToRed(this);
        }
    }
    // 使用
    main() {
        TrafficLight trafficLight = new TrafficLight();
        trafficLight.switchToGreen();
        ......
    }
    // 总结:状态模式的角色:状态规范标准(State),状态接口实现(Red等),持有状态接口的引用即系统环境(TrafficLight)

18 备忘录-Memento

  • 在不破坏元对象封装性的前提下,捕获该对象在某些时刻的内部状态,并像历史快照一样将它们保留在元对象之外,以备恢复只用

    // 文档类
    public class Doc {
        private String title;
        private String body;
        // getter、setter、构造器略
        
        // 生成快照记录
        public History createHistory() {
            return new History(this.body);
        }
        // 恢复历史记录
        public void restoreHistory(History history) {
            this.body = history.getBody();
        }
    }
    // 编辑器类
    public class Editor {
        private Doc doc;
        // getter、setter、构造器略
        public void append(String txt) {
            this.doc.setBody(this.doc.getBody + txt);
            show();
        }
        public void delete() {
            this.doc.setBody("");
            show();
        }
        public void save() { ...... }
        public void show() { ...... }
    }
    // delete()出于软件设计功能完整性考虑;在调用save()之前误用了delete()则可能给用户带来潜在风险;于是设计历史记录快照类用于记录每一步操作后的状态,当误操作以后的撤回可以帮助回溯历史
    public class History {
        private String body; // 备忘记录
        // 返回到Doc类中:文档操作生成备忘录对象,因此生成方法位于Doc中
        // 采用新定义类来记录而不是将元对象进行复制,处于节省内存空间的考虑,没有必要记录元对象中所有的数据信息
    }
    // 重构Editor
    public class Editor {
        // ...
        private List<History> historyRecords; // 历史记录列表
        private int historyPosition = -1; // 历史记录位置
        
        public Editor(Doc doc) {
            this.doc = doc;
            this.historyRecords = new ArrayList<>();
            backup();
            show();
        }
        public void append(String txt) {
            this.doc.setBody(this.doc.getBody + txt);
            backup();
            show();
        }
        public void delete() {
            this.doc.setBody("");
            backup();
            show();
        }
        // 备份记录
        public void backup() {
            this.historyRecords.add(this.doc.getBody());
            this.historyPosition++;
        }
        // 撤销
        public void undo() {
            if (this.historyPosition == 0) {
                return;
            }
            this.historyPosition--;
            History history = this.historyRecords.get(this.historyPosition);
            this.doc.restoreHistory(history); // 取出历史记录并恢复文档
            show();
        }
    }
    // 使用
    main() {
        Editor editor = new Editor(new Doc("文档"));
        editor.append(".......");
    }
    // 总结:备忘录模式的角色:元对象类(Doc),备忘录(History),备忘录的维护者即看护人(Editor)

19 中介-Mediator

  • 中介模式为对象架构出一个互动平台,通过减少对象间的依赖程度达到解耦

    // 强耦合的直接关联模拟
    public class People {
        private String name;
        privte People other; // 持有对方引用,强耦合
        // 建立连接
        public void connect(People other) {
            this.other = other;
        }
        public void talk(String message) {
            this.other.getName() + "对" + this.name + "说" + messge;
        }
    }
    // 使用
    main() {
        People p1 = new People("p1");
        People p2 = new People("p2");
        p1.connect(p2);
        p2.connect(p1); // 必须要先建立连接,才能与持有的连接通话
        p1.talk("...");
        p2.talk("...");
    }
    // 持有对方引用即强耦合,当对象数量增多或对象间关系复杂时将出现严重问题;必须避免多对多对象关联关系的陷阱
    // 构建第三方交互平台:抽离共有的持有引用放于平台的列表中统一维护
    public class User {
        private String name;
        private ChatRoon charRoon; // 聊天室引用
        
        public void login(ChatRoom chatRoom) {
            this.chatRoon = chatRoon;
            this.chatRoom.register(this); // 连接注册
        }
        
        public void talk(String message) {
            this.charRoom.sendMessage(this, message);
        }
        public void listen(User formWhom, String message) {
            fromWhom.getName() + "说" + message;
        }
    }
    public class ChatRoom {
        private String name;
        List<User> users = new ArrayList<>(); // 维护所以在平台上的User
        public void register(User user) {
            this.users.add(user);
        }
        public void sendMessage(User fromWhom, String message) {
            this.users.stream().forEach({
                toWhom -> toWhom.listen(fromWhom, message);
            });
        }
    }
    // 使用
    main() {
        ChatRoom chatRoom = new ChatRoom("平台");
        User user1 = new User("u1");
        User user2 = new User("u2");
        ......
        user1.login(chatRoom);
        user2.login(chatRoom);
        ......
        user1.talk("......");
        ......
    }
    // 继续重构
    ......

20 命令-Command

  • 命令指一个对象向其他多个对象发送指令,发送方负责下达指令,接收方接收指令并根据指令触发相应的行为

  • 命令模式将指令信息这一数据进行封装,然后作为参数传递给接收方,使得命令的请求方与执行方解耦;此外命令模式还支持命令批量执行,顺序执行或命令反执行等操作

  • ............

21 访问者-Visitor

  • 访问者模式主要用于解决数据与算法的耦合性问题;将算法独立归类,访问数据时根据数据类型自动切换对应的算法,确保算法功能的扩展和不污染数据

  • ...........

22 观察者-Observer

  • ............

23 解释器-Interpreter

  • ......
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值