设计模式的设计原则
设计模式就是为了实现这些原则,从而达到代码重用和增加可维护性的目的。这个原理是伯特兰·迈耶提出的。原文是:“软件实体应该对扩展开放,但对修改关闭”。也就是说,模块应该对扩展开放,对修改封闭。应该在不修改原始代码的情况下扩展该模块。那么如何扩张呢?再来看工厂模式:假设中关村有一个卖盗版光盘和色情片的小孩,我们给他设计一个光盘销售管理软件。我们应该先设计一个“CD”界面。如图所示:
[前]
______________
| & lt& gt|
| CD |
|_____________|
|+Sell () |
| |
|_____________|
[/pre]
盗版光盘和色情是它的子类。Kid通过“DiscFactory”管理这些CD。代号:公?班级?DiscFactory{public?静电?get disc(string name){ return(disc)类。forname(姓名)。new instance();}}有人想买盗版碟,怎么做?公共?班级?Kid {public?静电?作废?main(String[]?Args){ CD?d?=?DiscFactory.getDisc(盗版盘);d . sell();}}如果有一天,这小子良心发现,开始卖正版软件。没关系,我们只需要创建一个“光盘”、“正版软件”的子类,不需要修改原有的结构和代码。最近怎么样?对扩展开放,对修改封闭——“开放封闭原则”。
工厂模式是扩展特定的产品,有些项目可能需要更多的扩展。如果我们要扩大这个“工厂”,它就会变成一个“抽象工厂”。复合/聚合重用原则(CARP)通常被称为复合重用原则。组合/聚合复用的原理是在一个新对象中使用一些已有的对象,使其成为新对象的一部分;新对象通过委托给这些对象来实现重用现有功能的目的。它的设计原则是:尽量用合成/聚合,尽量不用继承。
也就是说,要少用继承,多用合成关系来实现。我曾经写过一个这样的程序:有几个类要处理数据库,所以我写了一个操作数据库的类,然后其他所有处理数据库的类都继承这个。结果后来我修改了一个数据库操作类的方法,所有的类都需要改。“牵一发而动全身”!面向对象就是把波动限制在尽可能小的范围内。
在Java中,你应该试着为接口编程,而不是实现类。这样,替换子类不会影响调用其方法的代码。让每个班级尽量少和别人接触,“不要和陌生人说话”。这样城门口的火就不会伤到池里的鱼了。可以提高可扩展性和可维护性。设计模式有三种类型,***23。创意模式:单体模式、抽象工厂、构建者模式、工厂模式、原型模式。结构模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享受模式和代理模式。行为模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介器模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式。按字典顺序简单介绍如下。
抽象工厂:提供了一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的特定类。
适配器(Adapter Mode):将一个类的接口转换成客户想要的另一个接口。适配器模式使由于接口不兼容而不能一起工作的类能够一起工作。
桥:将抽象部分与其实现部分分离,这样两者都可以独立更改。
构建器(builder mode):将一个复杂对象的构建与它的表示分离,这样同一个构建过程可以创建不同的表示。
责任链:分离请求的发送方和接收方,以便多个对象有机会处理请求。将这些对象连接成一个链,并沿着这个链传递请求,直到有对象处理它。
命令模式:将一个请求封装成一个对象,这样你就可以参数化客户的不同请求;排队或记录请求,并支持可取消的操作。
复合模式:将对象组合成一个树形结构来表示“部分-整体”层次结构。它使客户能够一致地使用单个对象和复合对象。
装饰模式:动态地给一个对象增加一些额外的责任。就扩展函数而言,比生成子类更灵活。
Facade:为子系统中的一组接口提供一致的接口。Facade模式定义了一个高级接口,使得这个子系统更容易使用。
工厂方法:定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法将类的实例化延迟到它的子类。
Flyweight (Enjoy元模式):利用* * * enjoy技术有效支持大量细粒度对象。
解释器(解析器模式):给定一种语言,定义其语法的表示,并定义一个解释器,解释器使用该表示来解释该语言中的句子。
迭代器(迭代器模式):提供了一种顺序访问聚合对象中的元素的方法,而不暴露对象的内部表示。
中介(中介模式):中介对象用于封装一系列对象交互。中介使对象没有必要显式地相互引用,从而使它们的耦合变得松散,并独立地改变它们的交互。
Memento模式:在不破坏封装的情况下,捕获对象的内部状态并保存在对象外部。这样,对象可以在以后恢复到保存状态。
观察者模式:定义对象之间一对多的依赖关系,这样当一个对象的状态改变时,所有依赖它的对象都会得到通知并自动刷新。
Prototype(原型模式):指定要用原型实例创建的对象的种类,并通过复制这个原型来创建一个新对象。
Proxy(代理模式):为其他对象提供代理,以控制对该对象的访问。
单例模式:保证一个类只有一个实例,并提供一个全局访问点来访问它。Singleton模式是最简单的设计模式之一,但是对于Java开发人员来说,它有很多缺陷。在9月的专栏中,David Geary讨论了单例模式,以及在多线程、类加载器和序列化面前如何处理这些缺陷。
状态模式:允许对象在其内部状态改变时改变其行为。该对象似乎已经修改了它所属的类。
策略:定义一系列算法,一个一个封装,使其可替换。这种模式使得算法的改变独立于使用它的客户。
模板法:定义运算中一个算法的骨架,将一些步骤延迟到子类。模板方法使子类能够在不改变算法结构的情况下重新定义算法的某些步骤。
访问者模式:表示作用于对象结构中每个元素的操作。它允许你在元素上定义新的操作而不改变它们的类。
从下一节开始,详细描述以下每个设计模式。目的
定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法将类的实例化延迟到它的子类。
适用性当一个类不知道它必须创建的对象的类时。当一个类希望它的子类指定它创建的对象时。当一个类将创建一个对象的责任委托给几个助手子类中的一个,并且您想要本地化哪个助手子类是代理的信息时。目的
提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的特定类。
适用性一个系统应该独立于其产品的创建、组合和呈现。当系统由多个产品系列之一配置时。当你想强调一系列相关产品对象的设计以供共同使用时。当你提供一个产品类库,只想展示它们的接口而不是实现的时候。目的
把一个复杂对象的构造和它的表示分开,这样同一个构造过程可以创建不同的表示。
当用于创建复杂对象的算法应该独立于对象的组件以及它们如何被组装时的适用性。当构造过程必须允许构造对象的不同表示时。目的
指定要用原型实例创建的对象种类,并通过复制这些原型来创建新对象。
当要实例化的类在运行时被指定时的适用性,例如,通过动态加载;或者避免创建与产品类层次结构平行的工厂类层次结构;或者一个类的实例只能有几种不同状态组合中的一种。构建相应数量的原型并克隆它们可能比每次用适当的状态手动实例化类更方便。目的
确保一个类只有一个实例,并提供一个全局访问点来访问它。
当一个类只能有一个实例,并且客户可以从一个众所周知的访问点访问它时的适用性。当这个唯一的实例应该通过子类化可扩展时,客户应该能够在不改变代码的情况下使用扩展的实例。目的
将一个类的接口转换成客户想要的另一个接口。适配器模式使由于接口不兼容而不能一起工作的类能够一起工作。
适用性您希望使用现有的类,但其接口不符合您的需求。您希望创建一个可重用的类,它可以与其他不相关的类或不可预测的类(即接口可能不兼容的类)一起工作。(仅对象适配器)您想要使用一些现有的子类,但是不可能将每个子类化以匹配它们的接口。对象适配器可以调整其父类接口。目的
将抽象部分与其实现部分分开,这样它们都可以独立地改变。
适用性您不希望抽象和它的实现之间有固定的绑定关系。例如,这可能是因为实现部分应该能够在程序运行时被选择或切换。类的抽象及其实现应该通过生成子类来扩展。此时,B r i d g e模式使您能够将不同的抽象接口和实现部分组合在一起,并分别进行扩展。抽象实现部分的修改应该对客户没有影响,也就是说,客户的代码不必重新编译。(C++)你想对客户完全隐藏抽象实现部分。在C++中,类的表示在类接口中是可见的。有许多类要生成。这样的类层次结构意味着你必须将一个对象分解成两部分。Rumbaugh将这种类层次结构称为“嵌套概括”。您希望* * *在多个对象之间共享实现(可能使用引用计数),但同时要求客户不要知道这一点。一个简单的例子是Coplien的String类,其中多个对象可以* * *享受相同的字符串表示(StringRep)。目的
将对象组合成一个树形结构来表示“部分-整体”层次结构。c,O,P,O,S,I,T,E使用户能够一致地使用单个对象和组合对象。
适用性您希望表示对象的一部分——整体层次结构。你希望用户忽略组合对象和单个对象的区别,用户会统一使用组合结构中的所有对象。目的
动态地给一个对象增加一些额外的职责。在添加函数方面,装饰器模式比生成子类更灵活。
适用性以动态和透明的方式向单个对象添加责任,而不影响其他对象。处理那些可以撤销的责任。当你不能通过生成子类来扩展它的时候。在一种情况下,可能会有大量的独立扩展,并且会产生大量的子类来支持每一种组合,使得子类的数量呈爆炸式增长。另一种情况可能是因为类定义被隐藏了,或者类定义不能用来生成子类。目的
为子系统中的一组接口提供一致的接口。Facade模式定义了一个高级接口,这使得子系统更易于使用。
适用性当您希望为复杂的子系统提供简单的接口时。子系统往往因为不断进化而变得越来越复杂。大多数模式在使用时会产生更多更小的类。这使得子系统的可重用性更强,更容易定制,但也给不需要定制子系统的用户带来了一些困难。Facade可以提供一个简单的默认视图,对于大多数用户来说已经足够了,而那些需要更多定制的用户可以超越Facade层。客户端程序和抽象类的实现部分有很大的依赖性。引入Facade将这个子系统从客户和其他子系统中分离出来,可以提高子系统的独立性和可移植性。当您需要构建分层子系统时,使用facade模式来定义子系统中每一层的入口点。如果子系统是相互依赖的,您可以让它们只通过Facade进行通信,从而简化它们之间的依赖关系。目的
使用* * * enjoy技术有效支持大量细粒度对象。
应用程序使用大量的对象。由于使用了大量的对象,造成了很大的存储开销。对象的大多数状态都可以改变为外部状态。如果删除一个对象的外部状态,可以用相对较少的* * *对象替换许多组对象。该应用不依赖于对象识别。由于Flyweight对象可以被* * *,所以对于概念上明显不同的对象,识别测试会返回真值。目的
为其他对象提供代理以控制对此对象的访问。
适应性
当您需要用更通用、更复杂的对象指针替换简单指针时,请使用代理模式。以下是可以使用代理模式一些常见情况:远程代理为不同地址空间中的对象提供本地表示。虚拟代理根据需要创建昂贵的对象。保护代理控制对原始对象的访问。当对象应该具有不同的访问权限时,使用保护代理。智能引用取代了简单指针,后者在访问对象时执行一些附加操作。它的典型用途包括:统计对实际对象的引用,以便当对象没有引用时,可以自动释放(也称为SmartPointers)。当一个持久对象第一次被引用时,它被加载到内存中。在访问实际对象之前,检查它是否已被锁定,以确保其他对象不能更改它。目的
使多个对象都有机会处理请求,从而避免了请求的发送方和接收方之间的耦合关系。将这些对象连接成一个链,并沿着这个链传递请求,直到有对象处理它。
适用性有多个对象可以处理一个请求,并且哪个对象处理该请求的运行时是自动确定的。您希望向多个对象之一提交请求,而不明确指定接收者。应该动态指定可以处理请求的对象集。目的
将一个请求封装成一个对象,这样就可以参数化不同请求的客户;排队或记录请求,并支持可取消的操作。
适用性与上面讨论的MenuItem对象一样,抽象要执行的操作以参数化对象。可以用过程语言中的回调函数来表达这种参数化机制。所谓回调函数,就是先把函数注册在某个地方,以后需要的时候再调用。命令模式是回调机制的面向对象的替代品。指定、安排和执行不同时间的请求。命令对象的生存期可以独立于初始请求。如果请求的接收者可以用独立于地址空间的方式来表达,则负责该请求的命令对象可以被转移到另一个不同的进程,并且该请求可以在那里实现。支持取消操作。Command的Execute操作可以存储操作实现前的状态,当操作取消时用这个状态来消除操作的影响。必须将执行操作添加到命令界面,这将取消上一次执行调用的效果。执行的命令存储在历史列表中。通过前后遍历这个列表并分别调用UnExecute和Execute,可以无限制地“取消”和“重做”。支持修改日志,这样当系统崩溃时,这些修改可以重做。在命令界面中增加加载操作和存储操作,可以用来保持一致的变更修改日志。从崩溃中恢复的过程包括从磁盘重新读取记录的命令,并使用执行操作重新执行它们。用建立在原始操作基础上的高级操作构建一个系统。这种结构在支持事务的信息系统中很常见。事务封装了一组对数据的更改。命令模式提供了一种对事务建模的方法。命令有一个公共接口,因此您可以用相同的方式调用所有事务。同时,使用这种模式很容易添加新的事务来扩展系统。目的
给定一种语言,定义其语法的表示,并定义一个解释器,解释器使用该表示来解释该语言中的句子。
适用性当有一种语言需要解释和执行,并且你可以把语言中的句子表示成一棵抽象的语法树时,你可以使用解释器模式。该模型在存在以下情况时效果最佳:语法简单,语法的类层次结构变得庞大,对于复杂的语法来说难以管理。这时候像解析器生成器这样的工具是比较好的选择。它们可以解释表达式,而无需构建抽象语法树,这样可以节省空间和时间。效率不是关键问题。最有效的解释器通常不是通过直接解释解析树来实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍然可以在解释器模式下实现,这仍然是有用的。目的
提供一种方法来顺序访问聚合对象中的元素,而不公开对象的内部表示形式。
适用性访问聚集对象的内容,而不暴露其内部表示。支持聚合对象的多次遍历。为遍历不同的聚合结构提供统一的接口(即支持多态迭代)。目的
用中介对象封装一系列对象交互。中介使对象没有必要显式地相互引用,从而使它们的耦合变得松散,并独立地改变它们的交互。
适用性一组对象以定义明确但复杂的方式进行通信。由此产生的相互依赖令人困惑,难以理解。一个对象引用许多其他对象并直接与它们通信,这使得重用该对象变得困难。想自定义一个分布在多个类中的行为,但不想生成太多子类。目的
捕获对象的内部状态,并在不破坏封装的情况下将其保存在对象外部。这样,对象可以在以后恢复到保存状态。
适用性必须保留对象在某一时刻的(部分)状态,以便在以后需要时可以恢复到以前的状态。如果一个人使用一个接口让其他对象直接获得这些状态,就会暴露对象的实现细节,破坏对象的封装。目的
定义对象之间的一对多依赖关系。当对象的状态改变时,所有依赖于它的对象都会得到通知并自动更新。
当一个抽象模型有两个方面,其中一个依赖于另一个时的适用性。将两者封装在单独的对象中,以便可以独立地更改和重用它们。当对一个对象的更改需要同时更改其他对象时,我不知道需要更改多少个对象。当一个对象必须通知其他对象时,它不能假定其他对象是谁。换句话说,您不希望这些对象紧密耦合。目的
允许对象在其内部状态改变时改变其行为。该对象似乎已经修改了它的类。
适用性一个对象的行为依赖于它的状态,它必须根据运行时的状态改变它的行为。一个操作包含一个巨大的多分支条件语句,这些分支依赖于对象的状态。这种状态通常由一个或多个枚举常数表示。通常,有多个操作包含相同的条件结构。状态模式将每个条件分支放入一个单独的类中。这样可以让你根据自身情况把一个对象的状态看成一个对象,这个对象可以独立变化,不依赖于其他对象。目的
定义一系列算法,一个一个封装,使之可替换。这种模式使算法能够独立于使用它的客户进行更改。
适用性许多相关的类只是行为不同。策略提供了一种用几种行为之一配置类的方法。你需要使用算法的不同变体。例如,您可以定义一些反映不同空间/时间权衡的算法。当这些变体作为算法的类层次结构实现时,可以使用策略模式。算法使用客户不应该知道的数据。策略模式可以用来避免暴露复杂的算法相关的数据结构。一个类定义了多种行为,这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移到它们各自的策略类中,以替换这些条件语句。目的
在运算中定义一个算法的骨架,将一些步骤延迟到子类。Te m p l a t e M e t h o d允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
适用性一次实现一个算法不可改变的部分,把可改变的行为留给子类。每个子类中男* * *的行为都要提取出来,集中在一个男* * *父类中,避免代码重复。这是奥普戴克和约翰逊描述的“分解到概括”的一个很好的例子。首先,识别现有代码中的差异,并将差异分离到新的操作中。最后,用调用这些新操作的模板方法替换这些不同的代码。控件子类扩展。模板方法只在特定的点调用“钩子”操作,只允许在这些点进行扩展。目的
表示作用于对象结构中的元素的操作。它允许你在元素上定义新的操作而不改变它们的类。
适用性一个对象结构包含许多具有不同接口的类对象,你想对这些依赖于它们特定类的对象执行一些操作。您需要对一个对象结构中的对象执行许多不同且不相关的操作,并且您希望避免这些操作“污染”这些对象的类。Visitor使您能够集中相关的操作,并在一个类中定义它们。当对象结构被许多应用程序共享时,使用访问者模式使每个应用程序只包含需要使用的操作。定义对象结构的类很少改变,但是经常需要在这个结构上定义新的操作。改变对象结构类需要为所有访问者重新定义接口,这可能代价很高。如果对象结构类经常变化,在这些类中定义这些操作可能更好。