design patterns

UML类图

类的关系

泛化(generalization)

继承非抽象类,箭头指向父类

泛化

实现(realize)

继承抽象类,箭头指向接口

实现

聚合(aggregation)

整体和部分不是强依赖的,B不存在,A仍可存在

聚合

组合(composition)

强依赖的,B不存在,A也不存在

组合

关联(association)

是一种拥有的关系,它使一个类知道另一个类的属性和方法。箭头指向被拥有者。

分为双向关联和单向关联。

老师与学生是双向关联,老师有多名学生,学生也可能有多名老师。但学生与某课程间的关系为单向关联,一名学生可能要上多门课程,课程是个抽象的东西他不拥有学生。

Indicator Meaning
0..1 Zero or one
1 One only
0..* Zero or more
* Zero or more
1..* One or more
3 Three only
0..5 Zero to Five
5..15 Five to Fifteen

关联

依赖(dependency)

描述一个对象在运行期间会用到另一个对象的关系

依赖

类的成员

画图顺序:类名,类成员变量,类的方法

  • public+

  • private-

  • protected#

  • static:underlined

    1559099511545

设计模式

一、创建型模式(Creational)

对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离

创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的

1.1 简单工厂模式(Simple Factory)

根据参数的不同返回不同类的实例。当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。

适用于工厂类负责创建的对象比较少的情况,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。

  • 优点:

    • 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易
    • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量
    • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性
  • 缺点:

    • 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
    • 使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
    • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护
    • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
  • 应用:

    • JDK类库中广泛使用了简单工厂模式,如工具类java.text.DateFormat,它用于格式化一个本地日期或者时间。

      1
      2
      3
      public final static DateFormat getDateInstance();
      public final static DateFormat getDateInstance(int style);
      public final static DateFormat getDateInstance(int style, Locale locale);
    • Java加密技术:

    1
    2
    3
    4
    // 获取不同加密算法的密钥生成器:
    KeyGenerator keyGen=KeyGenerator.getInstance("DESede");
    // 创建密码器:
    Cipher cp=Cipher.getInstance("DESede");

示例

1.2 工厂方法模式(Factory Method)

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。

如下图:某系统日志记录器要求支持多种日志记录方式,如文件记录、数据库记录等,且用户可以根据要求动态选择日志记录方式。

  • 优点:
    • 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
    • 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。
    • 客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
  • 缺点:
    • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
    • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

示例

1.3 抽象工厂模式(Abstract Factory)

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。

  • 优点:

    • 增加新的具体工厂和产品族很方便,无须修改已有系统
  • 缺点:

    • 增加新的工厂和产品族容易,增加新的产品等级结构麻烦

    示例

1.4 建造者模式(Builder)

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

用户使用不同的具体建造者即可得到不同的产品对象 。

与抽象工厂模式相比, 建造者模式返回一个组装好的完整产品 ,而 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。

建造者模式的结构中还引入了一个指挥者类Director,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。

缺点:如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

示例

1.5 单例模式(Singleton)

由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。

滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Widget
{
public:
Widget(const Widget &) = delete;
Widget(Widget &&) = delete;
Widget & operator=(const Widget &) = delete;
Widget & operator=(Widget &&) = delete;
protected:
Widget(){}
~Widget(){}
public:
static Widget & GetInstance()
{
static Widget w;
return w;
}
};

二、结构型模式(Structural)

描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

2.1 适配器模式(Adapter)

将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

适配器模式有对象适配器和类适配器两种实现。

系统需要使用现有的类,而这些类的接口不符合系统的需要时可用此模式。

  • 对象适配器:

对象适配器

  • 类适配器:

类适配器

  • 优点:
    • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
    • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
    • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类。
    • 对于类适配器:由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
    • 对于对象适配器:一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
  • 缺点:
    • 类适配器模式的缺点:对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类。
    • 对象适配器模式的缺点:与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

2.2 桥接模式(Bridge)

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。行为型模式

桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。

示例

2.3 装饰模式(Decorator)

将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为

动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。

装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。

使用装饰模式进行系统设计时将产生很多小对象,将增加系统的复杂度。

比继承更加易于出错,排错也很困难

示例

2.4 外观模式(Facade)

外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。

对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。

只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。

在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码。

示例

2.5 享元模式(Flyweight)

运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式。

享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。

享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

享元模式在编辑器软件中大量使用,如在一个文档中多次出现相同的图片,则只需要创建一个图片对象,通过在应用程序中设置该图片出现的位置,可以实现该图片在不同地方多次重复显示。

示例

2.6 代理模式(Proxy)

给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。

远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。

由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

虚拟代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

智能引用代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

图片代理:用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。

示例

三、行为型模式(Behavioral)

对在不同的对象之间划分责任和算法的抽象化。

3.1 命令模式(Command)

将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

很多系统都提供了宏命令功能,如UNIX平台下的Shell编程,可以将多条命令封装在一个命令对象中,只需要一条简单的命令即可执行一个命令序列,这也是命令模式的应用实例之一。

3.2 中介者模式(Mediator)

用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员(CommonMember)可以给其他会员发送文本信息,钻石会员(DiamondMember)既可以给其他会员发送文本信息,还可以发送图片信息。

chatGroup即为中介,会员们都通过中介交互。

在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

示例

3.3 观察者模式(Observer)

定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

示例

3.4 状态模式(State)

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换。

将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。

状态模式的使用必然会增加系统类和对象的个数。

示例

3.5 策略模式(Strategy)

定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。

一个系统需要动态地在几种算法中选择一种时可用此模式。

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

示例

参考