设计模式(未完)

UML类图及时序图

Unified Modeling Language,第三代建模和规约语言。用于说明、可视化、构建和编写一个正在开发的面相对象的、软件密集型系统的制品的开放方法。

类图

实箭继承,虚箭实现(可能有棒棒糖表示)

实现关联(成员变量中有其他类),虚线依赖(方法需要其他类作为参数)

空心菱形——聚合,实心菱形——组合

时序图:对象之间交互的图,按时间顺序排列

设计原则

  • 开闭原则

    类、模块和函数应该对扩展开放(新增类),对修改关闭。

    • 即用抽象构建框架,用实现扩展细节。

    优点:提高代码的可复用性和可维护性。

    如interface -> 实现类(实现基本方法) -> 子类(扩展细节)

  • 依赖倒置原则

    高层模块不应依赖底层模块,两者都应该依赖其抽象。

    • 即抽象不应该依赖细节,细节应该依赖抽象
    • 针对接口编程(新增不同的类去实现接口)而不是实现编程(在一个类中添加不同的方法,开闭原则中的修改关闭)。

    优点:减少类间耦合性、提高系统稳定性,提高代码可读性和可维护性,降低修改程序所造成的风险。

    如interface course -> implements java course, python course. student(interface course)新增课程就是新增实现interface course的类(开闭原则中的扩展开放),student的代码不需改,因为高层student只依赖底层抽象,是针对接口编程。

    Student student = new Student();
    student.setCourse(new JavaCourse());
    student.studyCourse();
    
    student.setCourse(new PythonCourse());
    student.studyCourse();
    
  • 单一职责原则

    不要存在多于一个导致类变更的原因(职责)。

    • 即一个类/接口/方法只负责一项职责。后两者尽量做到。

    优点:降低类的复杂性、提高类的可读性、提高系统的可维护性、降低变更引起的风险

  • 接口隔离

    多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。一个类/接口对一个类/接口的依赖应该建立在最小的接口上。当然,适度。

    优点:符合高内聚低耦合的思想,从而是类具有很好的可读性、可扩展性和可维护性。

    单一职责考虑的是实现,接口隔离考虑的是整个框架。

  • 迪米特原则(最少知道原则)

    一个对象应该对其他对象保持最少的了解。

    • 适度
    • 如果方法放在本类和外类都可以,那就本类。

    优点:降低耦合。

    朋友:出现在成员变量、方法的输入输出参数中的类

  • 里氏替换原则

    如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类。

    • 扩展就是子类可以扩展父类的功能,但不能改变父类原有的功能。
    • 在重载父类方法时,方法的输入要比父类的输入条件更宽。
    • 子类实现父类方法时(重写/重载/实现抽象)的输出要更严格。
    • 如果父子不符合里氏替换原则,则考虑创建一个共同父类/接口

    优点:约束继承泛滥,加强程序的健壮性。

  • 合成/聚合复用原则

    尽量使用对象组合/聚合,而不是继承关系达到复用的目的。

    优点:系统更灵活,降低类间耦合度。

    //组合
    public class FeedbackServiceImpl implements IFeedbackService {
    
        private final HbaseTemplate hbaseTemplate;
    
        @Autowired
        public FeedbackServiceImpl(HbaseTemplate hbaseTemplate) {
            this.hbaseTemplate = hbaseTemplate;
        }
    }  
     
    //聚合
    public class Society {
        private List<People> people; //一个家庭里有许多孩子
        // ...
    }
    

设计模式

工厂方法

定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,让类的实例化推迟到子类中进行。

  • 补充:与抽象工厂相比,更适合产品等级更新,但产品族也可以,只是没有抽象工厂那样清晰(划分好一个工厂生产一系列产品),应用时可能出现混淆(将不同族的产品组合)。

  • 场景

    • 创建对象需要大量的重复代码
    • 客户端/应用层不依赖于产品类实例如何被创建
    • 一个类通过其子类来指定创建哪个对象
  • 优缺点

    • 优:符合开闭原则。用户只需关心所需产品对应哪个工厂。
    • 缺:类容易过多。增加了系统的抽象性和理解难度。
  • 例子:video 有三个子类,那么就新建一个统一的video工厂,对三种video分别再新建三个继承video工厂的子类。这样需要video时,应用层直接调用对应的工厂即可。

抽象工厂

提供一个创建一系列相关或互相依赖对象(非具体类)的接口。

  • 补充:这样防止出现非同系列的组合。在扩展产品族时,需要增加新实现工厂,工厂内的产品类,旧的工厂和产品不需要改变。应用层只需依赖所需要的工厂类。但如果产品等级经常更新,就不适合抽象工厂,因为此时工厂接口,每个工厂实现都要更新产品。

  • 场景

    • 客户端/应用层不依赖于产品类实例如何被创建
    • 创建“一系列”对象(同一产品族)需要大量的重复代码
    • 提供一个产品类库,所有产品以同样的接口出现,从而使客户端不依赖具体实现
  • 优缺点

    • 优:用户无需关心创建细节。将一个系列的产品族统一到一起创建。
    • 缺:规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。增加了系统的抽象性和理解难度。
  • 例子:一个工厂接口,里面有一个产品族使用产品的生产方法。其子类是不同牌子的工厂,里面具体生产对应的产品类,如A牌子的空调和A牌子的冰箱。当然,A牌子空调和A牌子冰箱都从空调父类和冰箱父类继承而来。另一个牌子的结构一样。应用层需要哪个牌子的产品就调用哪个牌子的工厂即可。

建造者

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

  • 场景

    • 对象有非常复杂的内部结构
    • 想把对象创建和使用分离
  • 优缺点:

    • 优:用户只需指定需要创造的类型。封装性好,创建和使用分离。扩展性好、创建类之间独立。
    • 缺:产生多余的builder。产品内部发生变化时,建造者都需要修改。
  • 例子:内部构造者Computer类,里面有一个builder类。

//从下面方法可知,调用内部类ComputerBuilder中的方法设置内部类的成员变量(内部和外部成员变量重复),然后通过build调用外部类的构造方法,该方法传入ComputerBuilder,然后根据builder中成员变量值设置Computer中的成员变量值。
Computer computer = new Computer.ComputerBuilder().buildCPU("酷睿I7").buildMainBoard("华硕主板").build();

//ComputerBuilder中的build
return new Computer(this);
  • 例子:外部构造者ActualBuilder(继承Builder抽象类),computer类,Boss类。
    • ActualBuilder中有成员变量computer
    • Boss中有成员变量builder,有setBuilder和createComputer方法,里面是调用builder的方法设置其成员变量computer的成员变量,并返回builder的createComputer方法。
Builder builder = new ActualBuilder();
Boss boss = new Boss();
directorBoss.setBuilder(builder);
Computer computer = boss.createComputer("酷睿I7","华硕主板");

单例模式

保证一个类仅有一个实例,并提供一个全局访问点。

  • 补充:注意私有构造器,线程安全,延迟加载,序列化,反射。

  • 优缺点:

    • 优:减少内存开销。严格控制访问。
    • 缺:没有接口,扩展困难。
//懒汉,饿汉,枚举单例看Concurrent_Programming

//容器单例,非线程安全
public class ContainerSingleton {
    private ContainerSingleton(){...}
    private static Map<String,Object> singletonMap = new HashMap<String,Object>();
    public static void putInstance(String key,Object instance){
        if(StringUtils.isNotBlank(key) && instance != null){
            if(!singletonMap.containsKey(key)){
                singletonMap.put(key,instance);
            }
        }
    }
    public static Object getInstance(String key){
        return singletonMap.get(key);
    }
}

//线程单例
public class ThreadLocalInstance {
    private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
             = ThreadLocal.withInitial(() -> new ThreadLocalInstance());
    private ThreadLocalInstance(){}
    public static ThreadLocalInstance getInstance(){
        return threadLocalInstanceThreadLocal.get();
    }
}

原型模式

指定创建对象的种类,通过拷贝这些原型创建新的对象。(不调用构造函数)

  • 场景:
    • 类初始化消耗较多资源
    • new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
    • 构造函数比较复杂
    • 循环体中产生大量对象
  • 优缺点:
    • 优:比直接new对象性能高。简化创建过程
    • 缺:必须配备克隆方法,所以对克隆复杂对象或对克隆出的对象进行复杂改造时,有风险。
//下面例子中,如果saveOriginMailRecord想保存new生产的mail,且不在循环里面new对象(多次循环耗性能),可考虑克隆。克隆要让Mail类实现cloneable接口,默认浅克隆,最好用深克隆。
public static void main(String[] args) throws CloneNotSupportedException {
    Mail mail = new Mail();
    mail.setContent("初始化模板");
    for(int i = 0;i < 10;i++){
        Mail mailTemp = (Mail) mail.clone();
        mailTemp.setName("姓名"+i);
        mailTemp.setContent("xxx");
        MailUtil.sendMail(mailTemp);
    }
    MailUtil.saveOriginMailRecord(mail);
}
//深克隆,将里面的field也克隆
@Override
protected Object clone() throws CloneNotSupportedException {
    Mail mail = (Mail)super.clone();
    mail.name = mail.name.clone();
    mail.content = mail.content.clone();
    return mail;
}

外观模式

提供一个统一的接口,用来访问子系统中的一群接口。(定义一个高阶接口,让子系统更容易使用)

  • 场景:
    • 子系统复杂,需要多层结构简化。
  • 优缺点:
    • 优:简化过程,无需深入了解子系统。减少系统依赖。更好划分访问层次
    • 缺:增加/扩展子系统容易引入风险。不符合开闭原则。

外观模式是外界和子系统;中介者是子系统之间。

外观模式的外观对象做成单例模式。

外观模式通过抽象工厂获取子系统实例。

  • 例子:Computer、CPU和Disk类
    • Computer类中的成员变量有CPU和Disk类。有一个open和close方法,里面分别调用成员变量的方法。

装饰者模式

不改变原有对象的基础上,将功能附加到对象上。

  • 补充:提供了比继承更有弹性的替代方案(扩展原有对象功能)
  • 场景:
    • 扩展一个类的功能/职责
    • 动态添加和取消功能
  • 优缺点:
    • 优:继承的补充,比继承灵活,在不改变原对象的情况下给一个对象扩展功能。装饰类多层组合。符合开闭原则。
    • 缺:更多代码,特别动态装饰和多层装饰时更复杂。

装饰者关注动态添加方法,代理者关注控制访问权限。

装饰者可以和被装饰者实现相同接口,或者前者是后者的子类。适配器和被适配器可能有不同的接口。

  • 例子:一个煎饼抽象类,其子类有煎饼实现类和煎饼装饰类,煎饼装饰类的子类有加香肠装饰类和加鸡蛋装饰类。

    • 煎饼装饰类:成员变量有煎饼实现类,其继承的方法就改成调用这个实现类的方法。这个类是否抽象,看下面两个装饰类子类是否都要特定不一样的操作。
    • 加香肠装饰类:构造器取父类的煎饼实现类。实现继承方法时,调用父类方法并在此基础上增加功能。
    • 加鸡蛋装饰类:同上。
    //最终aBattercake的功能就是煎饼实现类 + 两个加鸡蛋装饰类 + 一个加香肠装饰类,即4个功能的组合。
    ABattercake aBattercake = new Battercake();
    aBattercake = new EggDecorator(aBattercake);
    aBattercake = new EggDecorator(aBattercake);
    aBattercake = new SausageDecorator(aBattercake);
    

适配器模式

将一个类的接口转换成客户期望的另一个接口,从而使原来不能兼容的类可以一起工作。

  • 场景:
    • 已存在的类的方法和需求不匹配时(方法结果相同或相似)
    • 不是设计时考虑,而是维护时考虑。
  • 优缺点:
    • 优:提高透明性和复用。目标类和适配器类解耦,提高扩展性。
    • 缺:增加复杂性。

适配器复用原有接口,添加一个协同工作的接口。外观模式也是新接口,但和原接口没太大关联。

  • 例子:有一个220V交流电类AC220。一个变压器接口(也可以是类,接口优先)。一个变压器接口实现类。
    • AC220:有一个outputAC220V方法。
    • 变压器接口:一个抽象方法outputDC5V
    • 变压器接口实现类:成员变量是AC220(也可以是实现AC220的接口),通过outputDC5V把AC220的outputAC220V方法的输出进行改造,如除以一个值,从而降低电压输出。

享元模式

提供了减少对象数量从而改善应用所需的对象结构。即运用共享技术有效地支持大量细粒度的对象。

  • 场景:

    • 系统底层开发,需要大量相似对象、缓冲池。
  • 优缺点:

    • 优:减少对象创建,省内存。
    • 缺:关注内外部状态和线程安全问题。复杂化。
  • 例子:Employee接口,其子类Manager。EmployeeFactory类

    • Employee有report方法

    • Manager有department和reportContent两个私有变量(外部状态,通过外部传入值来设置。如果是固定内容就是内部状态),有setReportContent和Manager(String department)的构造方法

    • EmployeeFactory有HashMap存Manager,有getManager(String department)方法,没有就new并put到HashMap中。

    • 这个Manager的HashMap就存放了不同的Manager,需要的时候取出而不是new。

组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构,从而使客户端对单个对象和组合对象保持一致的方式处理。

  • 场景:
    • 希望客户端忽略组合对象与单个对象的差异。
    • 处理一个树形结构
  • 优缺点:
    • 优:将对象分层。让客户端忽略层次差异。
    • 缺:限制类型时比较复杂。设计变得更抽象。
  • 例子:抽象类CatalogComponent,子类Course和CourseCatalog。
    • CatalogComponent定义了子类需要的大部分方法,不一定子类都需要,可能一些子类需要一些不需要,需要的重写方法,不需要的保留默认实现,比如抛出异常,说明没有这个方法的实现。

桥接模式

将抽象部分与它的具体部分实现部分分离,使它们都可以独立地变化。通过组合的方式建立两个类之间的联系。

  • 场景:
    • 抽象和具体实现之间增加更多灵活性
    • 一个类存在两个或以上独立变化的维度,且这些维度都需要独立进行扩展
    • 不希望使用继承或因为多层继承导致系统类的个数剧增
  • 优缺点:
    • 优:分离抽象及其具体部分。提高可扩展性。
    • 缺:增加难度,需要识别出系统两个独立变化维度。
  • 例子:有两个体系:实现体系Account,子类DepositAccount和SavingAccount;抽象体系Bank,子类ABCBank和ICBCBank。
    • Bank中有成员变量Account,构造方法要传入这个Account
    • ABCBank和ICBCBank的方法实现要调用Account的方法。
    • 这样,两个系统扩展就很方便。因为一个系统时,如果增加一个Account子类,每个Bank子类都要改。

代理模式

为其他对象提供一种代理,来控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。

  • 场景:
    • 保护目标对象
    • 增强目标对象
  • 优缺点:
    • 优:将代理对象和真实被调用的目标对象分离。一定程度降低了耦合度。增强目标对象。
    • 缺:类数目增加。请求速度变慢。复杂。
  • 例子:web应用的controller

模版方法模式

定义一个算法骨架,并允许子类为一个或多个步骤提供实现,从而使子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。

  • 场景:
    • 一次性实现一个算法的不变部分,将可变的留给子类。
    • 各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复。
  • 优缺点:
    • 优:复用,扩展。
    • 缺:类数目增加,增加系统实现复杂度。继承自身缺点,如果父类添加新的抽象方法,所有子类都要改。
  • 例子:模版类ACourse,子类DesignPatternCourse和FECourse
    • ACourse有一个makeCourse方法,规定了其他方法的执行顺序或条件。
    • 子类重写部分父类方法。子类可以添加成员变量,作为构造方法的参数,从而让应用层决定子类的一些行为(父类中方法执行条件以这些成员变量的值作为标准)。

迭代器模式

提供一种方法,顺序访问一个集合对象中的各个元素,而又不暴露该对象的内部表示。

  • 场景:
    • 访问一个集合对象的内容而无需暴露它的内部表示。
    • 为遍历不同的集合结构提供一个统一的接口
  • 优缺点:
    • 优:分离了集合对象的遍历行为。
    • 缺:类数目增加。

参考:
java设计模式精讲 Debug 方式+内存分析, Geely

posted @ 2018-10-29 17:57  justcodeit  阅读(209)  评论(0编辑  收藏  举报