面向对象笔记—设计模式

发布时间 2023-11-16 19:23:39作者: 吧拉吧拉吧

设计模式

一、概念

  • 设计模式是一系列在实践中总结出来的可复用的面向对象的软件设计方式
  • 设计模式就是描述一个反复出现的问题,以及解决这个问题的方案。可以重复使用这个解决方案而无须再做重复劳动。
  • 解决设计问题的固定套路
    • 重用,避免代码重复冗余
    • 优化体系结构
    • 提升系统的可维护性和弹性
    • 代码更加容易测试,利于测试驱动
    • 为性能优化提供便利
    • 使软件质量更加有保证
    • 增强代码可读性,便于团队交流
    • 有助于整体提升团队水平

二、Smalltalk MVC中的设计模式

  • 包括的三类对象:
    • 模型Model是应用对象
    • 视图View是它在屏幕上的表示
    • 控制器Controller定义用户界面对输入的响应方式
  • 意图:分离视图和模型,让多个视图可以共用一个模型
  • MVC主要使用是由Observer、Composite和Strategy设计模式

三、设计模式 、重构和Anti-parttern

  1. 设计模式:是成功经验和最佳实践的总结,指导设计人员采用
    正确精良的设计。
  2. 重构(Refactor):专注于软件的渐进完善。通过消除重复冗余代码,并将存在体系结构缺陷的代码重新构建成符合设计模式的代码来达到设计精良软件的目的
  3. Anti-parttern(反面模式)与设计模式相反,是失败教训的总结。其澄清了许多设计中经常面临的陷阱和容易混淆的问题,能有效防止开发人员犯错误,从而做出正确选择。

四、设计模式分类

  1. 创建型设计模式
    • 创建型模式抽象了实例化过程。允许程序动态创建对象。
    • 它们帮助一个系统独立于如何创建、组合和表示它的那些对象。
    • 对一组固定行为的硬编码(hard-coding)转移为定义一个较小的基本行为集
    • 优点
      • 它们都将关于该系统使用哪些具体的类的信息封装起来。
      • 它们隐藏了这些类的实例是如何被创建和放在一起的。
    • 包括:工厂模式、抽象工厂模式、单例模式
  2. 结构型设计模式
    • 关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。
    • 结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法。
    • Adapter:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
  3. 行为型设计模式
    • 关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。
    • 特点
      • 在代码中插入一些桩,在很大程度上以便用户控制
      • 创建型模式可以看作行为型模式的特例
    • 目的
      • 尽可能地把可复用的行为表示出来,而允许使用者定制需求变化的部分

几种设计模式

1. 适配器(Adapter)模式——结构型设计模式
  • 意图
    • 将一个类的接口转换成客户希望的另外一个接口
    • Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.
  • 别名:包装器Wrapper
  • 动机
    • 为复用而设计的工具箱类因为它的接口与专业应用领域所需要的接口不匹配,不能够被复用
  • 何时使用
    1. 系统需要使用现有的类,而此类的接口不符合系统的需要。
    2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
    3. 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
  • 结构
    • 类适配器使用多重继承
    • 对象匹配器依赖于对象组合
  • 例子:
    • 编辑器允许用户绘制和排列基本图元(线、多边型和正文等)生成图表。
      • 图形对象的抽象定义接口类Shape
      • 每一种图形对象定义了一个Shape的子类: LineShape类对应于直线,PolygonShape类对应于多边型,等等
    • 问题:
      • 要实现一个文本工具对象的TextShape
        • 用户界面工具箱已经提供了一个复杂的TextView类用于显示和编辑正文
        • 但TextView与Shape对象具有不同接口,无法直接复用
    • 解决方法:
      • 定义一个TextShape类,由它来适配TextView的接口和Shape的接口。
        • TextShape类继承Shape类的接口和TextView的实现
        • 将一个TextView实例作为TextShape的组成部分,并且使用TextView的接口实现TextShape
  • 适配器使用接口和多态,增加间接性
2. 工厂模式——创建型设计模式

(1) ABSTRACT FACTORY (抽象工厂)

  • 意图
    • 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 别名:Kit
  • 动机:考虑一个支持多种视感(look-and-feel)标准的用户界面工具包
  • 主要解决:接口选择的问题。
  • 主要解决:接口选择的问题。
  • 适用性
    • 一个系统要独立于它的产品的创建、组合和表示时。
    • 一个系统要由多个产品系列中的一个来配置时。
    • 当要强调一系列相关的产品对象的设计以便进行联合使用时。
    • 当提供一个产品类库,而只想显示它们的接口而不是实现时。
  • 参与者
    • 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
    • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
    • 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
    • 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
    • Client
      • 仅使用由AbstractFactory和AbstractProduct类声明的接口。
  • 抽象工厂模式通常涉及一族相关的产品,每个具体工厂类负责创建该族中的具体产品。客户端通过使用抽象工厂接口来创建产品对象,而不需要直接使用具体产品的实现类。
  • 示例:
    • 创建 Shape 和 Color 接口和实现这些接口的实体类。
    • 下一步是创建抽象工厂类 AbstractFactory。
    • 接着定义工厂类 ShapeFactory 和 ColorFactory,这两个工厂类都是扩展了 AbstractFactory。
    • 然后创建一个工厂创造器/生成器类 FactoryProducer
    • 最后创建AbstractFactoryPatternDemo 类使用 FactoryProducer 来获取 AbstractFactory 对象。向 AbstractFactory 传递形状/颜色信息,以便获取它所需对象的类型

(2) FACTORY METHOD (工厂方法)

  • 抽象工厂的简化,又称为简单工厂/具体工厂
  • 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
  • 问题:
    • 谁来创建适配器对象?谁来决定使用哪种类的适配器?
    • 如使用领域模型,无法保证领域对象的内聚,领域对象专注于相对单纯的应用逻辑职责
  • 解决方案:创建称为工厂的纯虚构对象来处理创建对象的职责
  • 优势:
    • 分离复杂创建的职责,并将其分配给内聚的帮助对象
    • 隐藏潜在的复杂创建逻辑
    • 允许引入提高性能的内存管理策略,例如对象缓存或再生

例子:描述使用工厂的解决方案

在ServicesFactory中,决定使用哪个类来创建的逻辑是,从外部源读取类的名称,然后动态装载这个类。
- 简单工厂方法的返回对象类型通常是接口,因此能返回接口的任何实现

  • 示例:
    • 创建一个 Shape 接口和实现 Shape 接口的实体类
    • 定义工厂类 ShapeFactory, 生成基于给定信息需要的实体类的对象
      public class ShapeFactory {
        //使用 getShape 方法获取形状类型的对象
        public Shape getShape(String shapeType){
            if(shapeType == null){
              return null;
            }        
            if(shapeType.equalsIgnoreCase("CIRCLE")){
              return new Circle();
            } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
              return new Rectangle();
            } else if(shapeType.equalsIgnoreCase("SQUARE")){
              return new Square();
            }
            return null;
        }
      }
      
    • 最后创建FactoryPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。
      public class FactoryPatternDemo {
        public static void main(String[] args) {
            ShapeFactory shapeFactory = new ShapeFactory();
            //获取 Circle 的对象,并调用它的 draw 方法,需要其他形状就修改getShape的参数
            Shape shape1 = shapeFactory.getShape("CIRCLE");
            //调用 Circle 的 draw 方法
            shape1.draw();
        }
      }
      
  • 分析小结:
    • 抽象工厂模式和工厂方法的不同点,从代码实现上看是抽象工厂在实体类与工厂类之间多了一个抽象工厂类。
    • 抽象工厂模式要解决的问题比较复杂,不但工厂是抽象的,产品是抽象的,而且有多个产品需要创建(如示例中有shape和color两个产品),因此,抽象工厂会对应到多个实际工厂,每个实际工厂负责创建多个实际产品
3. SINGLETON(单件)—对象创建型模式

工厂模式的设计引发的另一个问题,有谁来创建工厂自身,如何访问工厂?在工厂模式过程中,只需要一个工厂实例。其次在代码的不同位置调用工厂中的方法。这里就存在可见性问题:如何获得单一实例的全局可见性?

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  • 主要解决:一个全局使用的类频繁地创建与销毁。

  • 动机

    • 对一些类来说,只有一个实例是很重要的。
    • 怎么样才能保证一个类只有一个实例并且这个实例易于被访问呢?
    • 一个全局变量使得一个对象可以被访问,但它不能防止实例化多个对象。
    • 让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。这就是Singleton模式。
  • 适用性

    • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
    • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
  • 参与者

    • Singleton定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(即C + +中的一个静态成员函数)。
    • 可能负责创建它自己的唯一实例。
  • 协作

    • 客户只能通过Singleton的Instance操作访问一个Singleton的实例。
  • 实现

    • 保证一个唯一的实例:将创建这个实例的操作隐藏在一个类操作(即一个静态成员函数或者是一个类方法)后面,由它保证只有一个实例被创建
  • 关键代码:构造函数是私有的。

  • 实现方式

    1. 只有private构造方法,确保外部无法实例化;
    2. 通过private static变量持有唯一实例,保证全局唯一性;
    3. 通过public static方法返回此唯一实例,使外部调用方能获取到实例。
    4. 或者直接使用public static变量持有唯一实例(即将直接把static变量暴露给外部)
  • 示例:

    public class SingleObject {
      //创建 SingleObject 的一个对象
      private static SingleObject instance = new SingleObject();
      //让构造函数为 private,这样该类就不会被实例化
      private SingleObject(){}
    
      //获取唯一可用的对象
      public static SingleObject getInstance(){
          return instance;
      }
    
      public void showMessage(){
          System.out.println("Hello World!");
      }
    }
    
  • 注意:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化

  • 分析小结:

    • 单例的构造函数需要是私有的原因是为了防止了调用方自己创建实例
4.STRATEGY(策略)——行为型设计模式
  • 问题
    • 如何设计应对某一算法或业务规则的经常变化
  • 解决方案:
    • 使用独立的类分别定义实现每一种算法/政策/策略,并且这些类具有共同接口(用类实现算法)
  • 示例:POS定价策略
    • 问题:
      • 销售的定价策略具有多样性,存在大量变化.如某天的折扣、会员的折扣等,不同时期有不同的定价策略
      • 如何对各种各样的算法进行设计?
    • 分析:
      • 如会员折扣、节假日折扣,Sale的其他行为是一样,只是商品定价策略不同
      • 即定价策略是一种算法
    • 定价策略类
      • 创建多个SalePricingStrategy类,每个类具有同一接口:getTotal方法,将Sale对象作为参数,对sale打折前的价格应用打折规则
      • getTotal定价交互
        • 获取折扣价格的过程
        • 策略对象将依附于语境对象——策略对象对其应用算法。本例中,sale是语境对象,党getTotal消息被发送给sale时,会把部分工作委派给它的策略对象
          • 策略对象对于Sale对象有参数可见性;
        • 语境对象Sale对定价策略有属性可见性
  • 注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

题外话:这边推荐一下菜鸟教程廖雪峰的官方网站,举的例子很详细,狠狠的点赞