设计模式初探

        设计模式是对用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。
        设计模式需要有一些面向对象的语言基础以及足够耐心才会理解到并实践的一种方法。
        接下来的内容可能要慢慢读,并且很多遍才能理解其奥秘。祝大家代码能艺术。
        一个设计模式的命名、抽象和确定了一个统一设计结构的主要方面,这些设计结构能被用来构造可复用的面向对象设计。设计模式确定了所包含的类和实例,它们的角色、协作方式以及职责分配。每一个设计模式都集中于一个特定的面向对象设计问题或设计要点,描述了什么时候使用它,在另一些设计约束条件下是否还能使用,以及使用的效果和如何取舍。既然我们要最终实现设计,设计模式还提供了C++示例代码来阐明其实现。
        虽然设计模式描述的是面向对象设计,但它们都基于实际的解决方案,这些方案的实现语言是C++等主流面向对象编程语言,我们从实用角度出发选择了C++,因为在这些语言的使用上,我们积累了许多经验,况且它们越来越流行。

组织编目

        设计模式在粒度和抽象层次上各不相同。由于存在众多的设计模式,我们希望用一种方式将他们组织起来。
        我们根据两条准则对模式进行分类。第一是目的准则,即模式是用来完成什么工作的。模式基于其目的可分为创建型、结构型、或行为型三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为性模式对类或对象怎样交互和怎样分配职责进行描述。
        第二是范围准则,指定模式主要是用于类还是用于对象。类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻变确定下来了。对象模式处理对象见的关系,这些关系在运行时刻是可以变化的,更具动态性。从某种意义上来说,几乎所有模式都适用了继承机制,所有“类模式”只指那些集中于处理类间关系的模式,而大部分模式都术语对象模式的范畴。
        创新型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中。
        结构型类模式使用继承机制来组合类,结构型对象模式则描述了对象的组装方式。
        行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述一组对象怎样协作完成单个对象所无法完成的任务。

设计模式怎样解决设计问题

        设计模式采用多种方法解决面向对象设计者经常碰到的问题。这里给出几个问题以及使用设计模式解决他的方法。

1)寻找合适的对象

        面向对象程序由对象组成,对象包括数据和对数据进行操作的过程,过程通常称为方法操作。对象在收到客户的请求(或消息)后,执行相应的操作。
        客户请求是使对象执行操作的唯一方法,操作又是改变内部数据的唯一方法。由于这些限制,对象的内部状态是被封装的,他不能被直接访问,它的表示对于对象外部是不可见的。
        面向对象设计最困难的部分是将系统分解成对象集合。因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。
        面向对象设计方法学支持许多设计方法。你可以写出一个问题的描述,挑出名词和动词,进而创建相应的类和操作;或者,你可以关注于系统的协作和职责关系;或者,你可以对实现世界建模,再将分析时发现的对象转化为设计中。只与哪一种方法最好,并无定论。
        设计的许多对象来源于现实世界的分析模型。但是设计结果所得到的类通常在现实时间中并不存在,

2)决定对象的粒度

        对象在大小和数目上变化极大。他们能表示下自硬件或上自整个应用的任何事物。那么 我们怎样决定一个对象应该是什么呢?

3)指定对象接口

        对象声明的每一个操作指定操作名,作为参数的对象和返回值,这就是所谓的操作的型构。对象操作所定义的所以操作型构的集合被称为该对象的接口(interface)。对象接口描述了该对象所能接受的全部请求的集合,任何匹配对象接口中型构的请求都可以发送给该对象。
        类型(Type)是用来标识特定接口的一个名字。如果一个对象接受“Window”接口所定义的所有操作请求,那么我们就说该对象具有“Window”类型。一个对象可以有许多类型,并且不同的对象可以共享同一个类型。对象接口的某部分可以用某个类型来刻画,而其他部分则可用其他类型来刻画。两个类型相同的对象只需要贡献他们的部分接口。接口可以包含其他接口作为子集。当要给类型的接口包含另一个类型的接口时,我们就是它是另一个类型的子类型(subtype),另一个类型称之为它的超类型(supertype)。我们常说子类型继承了它的超类型的接口。
        在面向对象系统中,接口是最基本的组成部分。对象只有通过它们的接口才能与外部交换,如果不通过对象的接口就无法纸袋对象的任何事情,也无法请求对象做任何事情。对象接口与其功能实现是分离的,不同对象可以对请求做不同的实现,也就是说,两个有相同接口的对象可以有完全不同的实现。
        当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关。支持相同请求的不同对象可能对请求激发的操作有所不同的实现。发送给对象的请求和它的相应操作在运行时刻的链接就称之为动态绑定(dynamic binding)。
        动态绑定是指发送的请求直到运行时刻才受你的具体的实现的约束。因而,在知道任何有正确的接口对象都将接受此请求时,你可以写一个一般的程序,它期待着那些具有该特定接口的对象,进一步讲,动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称为多态(polymorphism)。它是面向对象系统中的核心概念之一。多态运行客户对象仅要求其他对象支持的特定接口,除此之外对其建设几近于无。多态简化了客户的定义,使得对象间彼此独立,并可以在运行时刻动态改变他们相互的关系。
        设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口。设计模式也许还会告诉你接口中不应包括哪些东西。
        设计模式也指定了接口之间的关系。特别地,他们经常要求一些类具有相似的接口;或他们对一些类的接口做了限制。

4)描述对象的实现

        至此,我们很少提及到实际上怎么取定义一个对象。对象的实现是由它的类决定的,类指定了对象的内部数据和表示,也定义了对象所能完成的操作。
        对象通过实例化类来创建,此对象被称为该类的实例。当实例化类时,要给对象的内部数据(由实例变量组成)分配存储空间,并将操作与这些数据联系起来。对象的许多类似实例是由实例化同一个类来创建的。
        新的类可以由已存在的类通过类继承来定义,当子类继承父类时,子类包含了父类定义的所有数据和操作。子类实例对象包含所以子类和父类定义的数据,且他们能完成子类和父类定义的所有操作。
        抽象类的主要目的时为他的子类定义公共接口。一个抽象类将它们的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作。非抽象类称为具体类。
        子类能够改进和重新定义他们父类的操作。更具体地说,类能够重定义(override)父类定义的操作,重定义使得子类能接管父类对请求的处理操作。类继承允许你只需简单的扩展其他类就可以定义新类。从而可以很容易地定义具有相近功能的对象族。
        混入类(mixin class)是给其他类提供可选择的接口或功能的类。它与抽象类不一样不能实例化,混入类要求多继承。

        1.类继承与接口继承的比较
                理解对象的类(class)和类型(type)之间的差别非常重要。
                一个对象的类定义了对象是怎样实现的,同时也定义了对象的内部状态和操作的实现。但是对象的类型只有它的接口有关,接口即对象能相应的请求的集合。一个对象可以有多个类型,不同类的对象可有相同的类型。
                当然,对象的类和类型是由紧密关系的。因为类定义了对象所能执行的操作,也定义了对象的类型。当我们说一个对象是一个类的实例时,即指该对象支持类所定义的接口。
                理解类继承和接口继承(或子类型化)之间的差别也十分重要。类继承根据一个对象的实现定义了另一个对象的实现。简而言之,它是代码和表示的共享机制。然而,接口继承(或子类型化)描述了一个对象什么时候能被用来替代另一个对象。
                C++中的接口继承的标准方法是公有继承一个含(纯)虚函数称为函数的类。C++中纯接口继承接近于共有继承纯抽象类,纯实现继承或纯类继承接近于私有继承。
尽管大部分程序设计语言并不区分接口继承和实现继承的差别,但使用中人们还是分别对待他们的。很多设计模式依赖于这种差别。

        2 对接口编程,而不是对实现编程
                类继承是一个通过复用父类功能而扩展应用功能的基本机制。它允许你根据旧对象快速定义新对象。它允 许你从已存在的类中继承所需要的绝大部分功能,从而几乎无需任何代价就可以获得新的实现。
        然而,实现类的复用只是成功的一半,继承所拥有的定义具有相同接口的对象族的能力也是很重要的(通常可以从抽象类来继承)。为什么?因为多态依赖于这种能力。
        当继承被恰当使用时,所有从抽象类导出的类将共享该抽象类的接口。这意味着子类仅仅添加或重定义操作,而没有隐藏父类的操作。这时,所有的子类都能影响抽象类中的请求,从而子类的类型都是抽象类的子类型。
        只根据抽象类中定义的接口来操纵对象有以下两个好处:
                1,客户无须知道他们使用对象的特定类型,只须对象有客户所期望的接口。
                2,客户无须知道他们使用的对象是用什么类来实现的,他们只须知道定义接口的抽象类。
        这将极大地减少子系统实现之间的相互依赖关系,也产生了可复用的面向对象设计的如下原则:
                针对接口编程,而不是针对实现编程。
        不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口。
        当你不得不系统的某个地方实例化具体的类(即指定一个特定的实现)时,创建型模式可以帮你。通过抽象对象的创建过程,这些模式提供不同方式以在实例化时建立接口和实现的透明连接。创建型模式确保你的系统是采用针对接口的方式书写的,而不是针对实现而书写的。

5) 运用复用机制

应用程序

        如果你将要建造像文档编辑器或电子制表软件这样的应用程序(Application Program),那么它的内部复杂性、可维护性和可扩充性是要优先考虑的。内部复用性确保你不会做多余的设计和实现。设计模式通过减少依赖性来提高内部复用性。松散耦合也增强了一类对象与其他多个对象协作的可能性。例如,通过孤立个封装每一个操作,以消除对特定操作的依赖,可使在不同上下文中复用一个操作变得更简单。消除对算法和表示的依赖可到到同样的效果。
        当设计模式被用来对系统分层和限制对平台的依赖性时,它们还会使一个应用更具可维护性。通过显示怎样扩展类层次结构和怎样使用对象复用。他们可增强系统的易扩充性。同时,耦合程度的降低也会增强可维护性,如果一个类不过多地依赖其他类,扩充这个孤立的类还是很容易的。

工具箱

        一个应用经常会使用来自一个或多个被称为工具箱(Toolkit)的预定义类库中的类。工具箱是一组相关的、可复用的类的集合,这些类提供了通用的功能。工具箱的一个典型例子就是列表、关联表单和堆栈等类的集合,C++的I/O流库是另一个例子。工具箱并不强调应用采用某个特定的设计,他们只是为你的应用提供功能上的帮助。工具箱强调的是代码复用,他们是面向对象环境下的“子程序库”。
        工具箱的设计比应用设计要难的多。应为他要去对许多应用是可用的和有效的。再者,工具箱的设计者并不知道什么应用使用该工具箱及他们有什么特殊需求。这样,避免假设和依赖就变得很重要。否则会限制工具箱的灵活性,进而影响它的适用性和效率。

框架

        框架(Framewok)是构成一类特定软件可复用设计的一组相互协作的类。例如,一个框架能帮助建立适合不同领域的图形编辑器,想艺术绘画、音乐作曲和机械CAD。另一个也许能帮助你建立财务建模应用。你可以定义框架抽象类的应用相关的子类,从而将一个框架定制为特定应用。
        框架规定了你的应用的体系结构。他定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者或实现者能集中精力与应用本身的细节。框架记录了其应用领域的共同的设计决策,因而框架更强调设计复用,尽管框架长包括具体的立即可用的子类。

—–未完,待续。。。

猜你喜欢

转载自blog.csdn.net/webster_wxh/article/details/80328723