剑客
关注科技互联网

设计模式基础

最近在负责一个新项目的基础搭建,过程中涉及到一些模块的划分、设计,之前也确实没有系统的学习过设计模式相关的知识,因此就花了些时间看了下设计模式,记录下来。本篇为设计模式的一些基础概念,但我觉得受益匪浅,当你掌握了基本概念和一些理论,才能更好地去从更高的层次去理解、设计一个模块,一些新知识的学习也是如此,Rx、RN,这些框架的思想都很重要,当然最终还是要回归到实践,才能更深地理解和掌握某一个新技术和新知识点。后面会有设计模式相关的文章,除了一些设计模式的具体介绍也会对一些优秀开源库从设计方面做一些分析,有兴趣的,可以关注下。

一 设计模式的分类

目的准则

模依据其目的可分为创建型 (Creational)、 结构型(Structural) 、 或行为型(Behavioral) 三种。

  • 创建型模式与对象的创建有关
  • 结构型模式处理类或对象的组合
  • 行为型模式对类或对象怎样交互和怎样分配职责进行描述

范围准则

指定模式主要是用于类还是用于对象

  • 类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了
  • 对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性
  • 创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一 个对象中

设计模式基础

二 描述对象的实现

2.1.类继承与接口实现的比较

一个类定义了对象的实现,同时也定义了对象的内部状态和操作的实现。

对象类型只与它的接口有关,接口即是对象能响应请求的集合。一个对象可以有多个类型,不同的对象可以有相同的类型。

类继承根据一个对象的实现定义另一个对象的实现。简而言之,它是代码和表示的共享机制。而接口继实现(或类型化)描述一个对象什么时候能替换另一个对象。

2.2.针对接口编程,而不是针对实现编程

类继承是通过复用父类功能扩展应用功能的基本机制,创建和使用都比较简单。但为了让继承体系拥有相同接口的对象的能力很重要,因为多态依赖这种能力,实现动态绑定。为了达到这种能力,可以通过抽象类来实现,所有从抽象类导出的类将共享该抽象类的接口。

只根据抽象类中定义的接口来操纵对象有以下两个好处:

  • 使用者无须知道对象的特定类型,只须对象有他们所期望的接口
  • 使用者无须知道对象是用什么类来实现的,他们只须知道定义接口的抽象类

这将极大地减少子系统实现之间的相互依赖关系,也产生了可复用的面向对象设计的原则:

针对接口编程,而不是针对实现编程

通常我们不应该将变量声明为某个特定的具体类对象,而是让它遵从抽象类所定义的接口。

当你不得不在系统的某个地方实例化具体的类 (即指定一个特定的实现 )时,可采用创建型模式。创建型模可以确保我们的系统针对接口编程,而不是针对实现。

三 运用复用机制

理解对象、接口、类和继承之类的概念并不难,问题的关键在于如何运它们写出灵活的、可复的软件。

3.1 继承和组合的比较

面向对象系统中功能复用的两种最常用技术是 类继承对象组合(object composition)

类继承

类继承允许你根据现有的类来定义一个类的实现。这种通过生成子类的复用通常被称为 白箱复用 (white-box reuse)。“白箱”是相对可视性而言:在继承方 式中,父类的内部细节对子类可见。

  • 优点

    类继承是在编译时刻静态定义的,且可直接使用,也可以方便的实现复用。当子类定义了部分操作时,它也能影响子类的操作。

  • 缺点

    静态的

    因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。

    破坏了封装性

    父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,导致父类实现中的任何变化必然导致子类发生变化。

对象组合

对象组合是类继承之外的另一种复用选择。复杂的功能可以通过组装或组合对象来获得。

这种复用风格被称为 黑箱复用 (black-box reuse),因为对象的内部细节是不可见的。

  • 优点

    良好的封装性

    组合要求对象遵守彼此的接口约定,而这些接口并不妨碍你将一个对象和其他对象 一起使用。因为对象只能通过接口访问,所以并不破坏封装性。而且由于对象的实现是基于接口写的,所以实现上存在较少的依赖关系。

    较小规模的类层次

    对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装并被集中在单个任务上。因此,基于对象组合的设计会有更多的对象 (而有较少的类),这样类和类继承层次会保持较小规模,并且不太可能增长为不可控的庞然大物。

    动态性

    对象组合是通过获得对其他对象的引用而在 运行时刻动态 定义的。且系统的行为将依赖于对象间的关系而不是被定义在某个类中,只要类型一致,运行时刻还可以用一个对象来替代另一个对象。

  • 缺点

    动态的、 高度参数化的软件比静态软件更难于理解

    因为存在间接性,运行效率低效

基于以上优点,在实际开发中,因优先使用组合、而不是继承

3.2 委托

委托(delegation) 是一种组合方式,它使组合具有与继承同样的复用能力。

在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者(delegate)。这类似于子类将请求交给它的父类处理。

  • 优点

    在于它便于 运行时刻 组合对象操作以及改变这些操作的组合方式。

  • 缺点

    与对象组合以取得软件灵活性的技术一样,动态的、 高度参数化的软件比静态软件更难于理解

    因为存在间接性,运行效率较低

委托的效率与上下有关,也与开发者的个人经验有关。委托最适于符合特定的情形,即标准模式的情形。有一些模式使用了委托,如 StateStrategyVisitor

委托是对象组合的特例。它告诉你对象组合作为一个代码复用机制可以替代继承。

四 设计应支持变化

为了避免后期的重构和重新设计,构建健壮性的系统,我们在写代码之前必须考虑系统在它的生命周期内会发生怎样的变化。设计模式可以确保系统能以特定方式变化,从而帮助我们避免重新设计系统。每一个设计模式可以确保系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮。

下面阐述一些导致重新设计的一般原因,以及解决这些问题的设计模式:

1) 通过显式地指定一个类来创建对象

这种创建对象的方式使得我们的代码受限于特定实现而不是接口。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象。

设计模式: AbstractFactory,FactoryMethod,Prototype

2) 对特殊操作的依赖

当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便地改变响应请求的方法。

设计模式: ChainofResposibility,Command

3) 对硬件和平台的依赖

依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新。所以设计系统时限制其平台相关性就很重要了。

设计模式: AbstractFactory ,Bridge

4) 对对象表示或实现的依赖

知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。

设计模式: AbstractFactory ,Bridge,Memento,Proxy

5) 算法依赖

算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。

设计模式: Builder 、Interator、Strategy、TemplateMethod、Visitor

6) 紧耦合

紧耦合的类很难独立地被复用,因为它们是互相依赖的。要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统很难学习、移植和维护。

松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。

设计模式: AbstractFactory ,Command , Facade, Mediator,Observer ,Chain of Responsibility

7) 通过生成子类来扩充功能

通常很难通过定义子类来定制对象。每一个新类都有固定的实现开销(初始化、终止处理等)。定义子类还需要对父类有深入的了解。并且子类方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。

对象组合技术和委托技术是继承之外组合对象行为的另一种灵活方法。新的功能可以通过以新的方式组合已有对象来实现。但是,如果过多地使用对象组合会使设计难于理解。

设计模式: Bridge, Chain of Responsibility ,Composite, Decorator,Observer , Strategy

8) 不能方便地对类进行修改

有时你需要修改一个类,但是却没有源代码。或者可能对类的任何改变会要求修改许多已存在的其他子类。

设计模式: Adapter, Decorator , Visitor

五 如何选择设计模式

我觉得首先就是对设计模式有一些了解,明白它们的分类、意图、使用场景,然后对遇到的问题,场景进行抽象,找出变化的地方,然后选择适当的设计模式将这些变化进行封装。

当然,设计模式不应该被滥用。因为通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更复杂并 /或牺牲了一定的性能。一个设计模式只有当它提供的灵活性是真正需要的时候,才有必要使用。

下图列出了设计模式允许独立变化的方面,你可以改变它们而不会导致重新设计:

设计模式基础

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址