设计模式

目录

一、设计模式概述

二、软件可复用问题和面向对象设计原则

三、设计模式的应用

1、工厂方法模式

简单工厂模式包含三个角色:

工厂方法模式包含以下四个角色:

2、代理模式

四、总结


一、设计模式概述

       设计模式(Design Pattern)是人们在长期的软件开发中对一些经验的总结,是对某些特定问题经过实践检验的特定解决方法。就像兵法中的三十六计,总结了36种对于战争中某些场合的可行性计谋战术—— “围魏救赵” “声东击西” “走为上” 等,可以说三十六计中的每一计都是一种模式。

       设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术方案总结成设计模式,也会使其他开发者更加容易理解其设计思路。设计模式是可复用的面向对象软件的基础,帮助开发者做出有利于系统复用的选择,避免损害系统复用性的设计。简言之,设计模式可以帮助设计者更快更好地完成系统设计。

       目前所说的设计模式通常是指GoF设计模式。GoF(Gang of Four,四人组)指的是Design Patterns: Elements of Reusable Object-Oriented Software 这本书的4位作者:Gamma、Helm、Johnson和Vlissides,书中总结了23种经典的设计模式,因此也被称为GoF设计模式。

       这23种设计模式有两种分类方式。

     (1)根据目的划分,即根据设计模式是用于完成何种工作来划分。这种方式可分为创建型模式、结构型模式和行为型模式3种。

    ▶    创建型模式:用于描述“如何创建对象”,其主要特点是 “将对象的创建与使用分离”。

    ▶    结构型模式:用于描述如何将类或对象按某种布局组成更大的结构。

    ▶    行为型模式:用于描述类或对象之间如何相互协作,共同完成单个对象无法独立完成  的任务,以及如何分配职责。

    (2)根据作用范围划分,即根据设计模式主要作用于类上还是主要作用于对象上来划分。这种方式可分为类模式和对象模式两种。

    ▶    类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编  译时刻便确定下来了。

    ▶    对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行  时是可以变化的,更具动态性。

二、软件可复用问题和面向对象设计原则

        众所周知,在软件开发和使用的工程中,需求是经常变化的。面对这些变化,设计不足的软件往往难以修改甚至要重新设计。

        大多数的软件应用是由多个类通过彼此合作才能实现完整的功能。对于组件化开发的软件来说,组件之间会存在各种依赖关系。例如,在A类的方法中,调用了B类对象的方法以完成特定的功能,我们就说A类依赖于B类。类与类之间的依赖关系增加了程序开发的复杂程度,一个类的变更,可能会对正在使用该类的所有类产生影响。以常见的业务层调用数据访问层的操作为例,代码通常如下所示。

             /**

               *  新闻模块的DAO接口

               */

              public interface NewsDao  {

                       /**

                        * 保存新闻信息的方法

                        */

                       public void save(News news);

                 }

                  /**

                   *  新闻模块的DAO实现类

                   */

                  public class NewsDaoImpl implements NewsDao  {

                        private Logger logger = Logger.getLogger(NewsDaoImpl.class);

                        public void save(News news) {

                              logger.debug("保存新闻信息到数据库");

                              ······// 省略数据库操作代码

                        }

                  }

                  /**

                   * 新闻模块业务类

                   */

                  public class NewsServiceImpl implements NewsService {

                        // 实例化所依赖的NewsDao对象

                        private NewsDao dao = new NewsDaoImpl();

                        public void addNews(News news) {

                              // 调用NewsDao的方法保存新闻信息

                              dao.save(news);

                         }

                   }

         以上代码中,NewsServiceImpl对NewsDao接口存在依赖关系,并且与其实现类NewsDaoImpl耦合在一起。此类常见的代码其实存在一个严重问题,即如果因为需求变化需要替换NewsDao的实现类,将导致NewsServiceImpl中的代码也要进行修改。由此不难想象,如果程序中比较基础的模块发生变化,将导致该模块的所有调用者都要修改代码,影响了其他模块的重用。如此,程序将难以扩展和维护,甚至难以开发、测试。

        对于如何设计易于维护和扩展的软件系统,面向对象的软件设计提出了几大原则。这些原则可以用来检验软件系统设计的合理性,也被设计模式所遵循。

1.  单一职责原则

       单一职责原则规定一个类应该有且仅有一个引起它变化的原因,简单来说,一个类应该只负责一个职责;否则,类应该被拆分。

       该原则提出一个类不应该承担太多职责。如果一个类承担了太多的职责,至少存在以下两个缺点。

    (1)一个职责的变化可能会影响这个类实现其他职责的能力,或者引发其他职责故障。

    (2)当客户需要该类的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余或风险。

 2.  开闭原则 (对扩展开放,对修改关闭)

       开闭原则是面向对象设计中最基础的设计原则,开闭原则规定一个软件实体,如类、模块和函数,应该对扩展开放,对修改关闭。其意思是,在程序需要进行拓展的时候,不能通过修改已有的代码实现变化,而应该通过扩展软件实体的方式实现,如根据需求重新派生一个实现类。想要达到这样的效果,这就需要使用接口,面向接口编程。

    在软件的生命周期内,因为变化、升级和维护等原因而对软件原有代码进行修改,可能会向原有代码中引入错误,也可能不得不对原有代码整个进行重构,并且原有代码修改后还要重新进行测试。

 3.  里氏替换原则

       里氏替换原则是面向对象设计的基本原则之一,是继承复用的基石。该原则规定所有引用基类的地方能透明地使用其子类的对象。简单来说,所有使用基类代码的地方,如果换成子类对象还能够正常运行,则满足这个原则:否则就是继承关系有问题,应该取消原来的继承关系,重新设计它们之间的关系。这个原则可以用来判断继承关系是否合理

 4.  依赖倒置原则(依赖于抽象而不依赖于具体实现,针对接口编程)

       依赖倒置原则的核心思想是:依赖于约定而不依赖于具体实现,即面向接口编程。对象的依赖关系有 3 种传递方式。

     (1)通过构造方法传递依赖对象,即构造方法的参数是需要依赖的接口类型。

     (2)通过setter方法传递依赖对象,即setter方法的参数是需要依赖的接口类型。

     (3)接口声明依赖,即接口方法的参数是需要依赖的接口类型。

       如果开闭原则是面向对象设计的目标,那么依赖倒置原则就是实现开闭原则的重要途经之一,它降低了客户与实现模块之间的耦合。

 5.  接口隔离原则

        接口隔离原则要求尽量将庞大臃肿的接口拆分成更小、更具体的接口,让接口中只包含客户感兴趣的方法。客户不应该被迫去依赖他不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用;否则,很多实现类被迫去实现它们不需要的方法。

       接口隔离原则和单一职责原则都是为了提高类的内聚性,降低它们之间的耦合度,但两者是不同的。

  1. 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
  2. 单一职责原则主要是约束类,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

 6.  迪米特法制

        迪米特法制又称为最少知道原则,是指一个软件实体应当尽可能少地与其他实体发生相互作用。具体来讲,被依赖的类应尽量将复杂逻辑封装在类的内部,不对外泄露任何中间信息,使客户对中间过程中的其他实体保持最少的了解,从而减少不必要的依赖,降低耦合。

7.  合成复用原则

        合成复用原则是指:尽量使用组合/聚合的方式,而不是继承关系达到软件复用的目的

        继承复用是类型的复用,必须具备is-a关系才可通过继承方式进行复用,且从基类继承而来的实现是静态的,不可能在运行期间发生变化,因此没有足够的灵活性。

        而合成复用是 has-a关系,将已有对象纳入到新对象中使之成为新对象的一部分,因此新对象可以调用已有对象的功能。使用合成复用方式,新对象可以在运行期间动态地引用与成分对象类型相同的实现。

三、设计模式的应用

1、工厂方法模式

       分析上面的代码片段所提出的问题,症结在于NewsServiceImpl的代码中明确创建了NewsDao接口的具体实现类NewsDaoImpl的实例,导致NewsServiceImpl类和NewsDaoImpl类紧密耦合在一起,丧失了使用的灵活性。而解决思路是避免在NewsServiceImpl中创建具体的NewsDao实现类,将创建工作转移出来,避免NewsServiceImpl和任何一个NewsDao实现类耦合。

        NewsServiceImpl需要一个NewsDao的实例。如果把这个实例看作产品,那么负责创建这个实例的组件就可以理解为生产这个产品的工厂,如下所示:

定义负责创建NewsDao实例的工厂类。关键代码如下:

        /**

         * 创建NewsDao实例的工厂类

         */

        public class SimpleDaoFactory {

              /**

               * 创建NewsDao实例的工厂方法

               */

              public static NewsDao getInstance() {

                      return new NewsDaoImpl();

              }

        }

       上面所展示的是简单工厂模式,又叫做静态工厂方法模式,它不属于 GoF 的 23 种设计模式之一。简单工厂模式是工厂模式家族中最简单的一种设计模式,可以理解为工厂模式的一个特殊实现。

简单工厂模式包含三个角色:

     (1)工厂 (Factory):  简单工厂模式的核心,负责实现创建所有实例的逻辑。工厂类是用来制造产品的。因此,在Factory中有一个用于制造产品的Create函数或者Generate函数之类的函数(这里的CreateProduct)。这个函数能够根据“标识符”(这里的ProductType)的不同生成不同的ConcreteProduct,当然这些ConcreteProduct都是继承自AbstractProduct的。

     (2)抽象产品 (Product):工厂创建的所有实例的父类,是负责描述所有产品的公共接口。可以是接口或抽象类。抽象产品是从其他具体产品抽象出来的。抽象产品类只有一个。

     (3)具体产品类 (Concrete Product):抽象产品的实现类,是工厂的创建目标。具体产品类继承自抽象产品类,可以有多个。当需要增加新的产品的时候就增加一个继承自抽象产品类的具体产品类即可。

工厂方法模式包含以下四个角色:

      (1)Product(抽象产品):定义了产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的父类。

      (2)ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。

      (3)Factroy(抽象工厂):在抽象工厂类中声明了工厂方法(Factory method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。

      (4)ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了在抽象工厂中声明的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

2、代理模式

        在生活中,我们经常听说房产中介、婚介、经纪人等社会角色,这些都是代理模式的实际体现。这种模式其实也是单一职责原则的体现,就好像一个人要买房,中间会涉及很多的环节,部分流程复杂而且专业。如果都由买家独自完成,也许很难做好,事情多影响本职工作不说,还不够专业很容易出问题。这时候,房产中介就可以发挥他的作用了。通过房产中介,买家专注看房、签合同,而中介则联系房源、推进流程。这样做可以分工明确,合作共赢。这就是代理模式典型的任务场景。

       代理模式包含如下角色:

       ▶    抽象主题(Subject):通过接口或抽象类声明业务方法。

       ▶    真实主题(Real Subject):实现了抽象主题中的具体业务,是实施代理的目标对      象,即代理对象所代表的真实对象,是最终要引用的对象。

       ▶    代理(Proxy):提供了与真实主题相同的接口,其内部含有对真实主题的引用,可以访问、控制或扩展真实主题的功能。

       实现代理模式有多种方法,总体上分为静态代理和动态代理两种。

       ▶    静态代理由开发者针对抽象主题编写相关的代理类实现,编译之后生成代理类的class文件。静态代理在编译时就已经实现,代理关系在编译期就已经绑定,编译完成后代理类是一个实际的class文件。

       ▶    动态代理是在运行时动态生成的,即编译完成后没有实际的代理类class文件,而是在运行时动态生成代理类字节码,并加载到JVM中。

四、总结

  • 单一职责原则要求实现类要职责单一;开闭原则要求对扩展开放、对修改关闭;里氏替换原则要求继承体系的合理性;依赖倒置原则要求面向接口编程;接口隔离原则要求接口设计要精简单一;迪米特法则要求减少不必要的中间过程的依赖;合成复用原则要求多用组合/聚合、少用继承。
  • 设计模式是软件开发中对于特定问题的经验总结,是针对特定问题经过实践检验的解决方案。
  • 简单工厂模式由一个工厂类负责所有产品的创建,客户只需要知道工厂和产品的父类即可。简单工厂返回的数据类型都是父类类型,工厂方法一般是静态的。
  • 工厂方法模式中一个具体工厂对应一个具体产品,符合开闭原则。
  • 代理模式可以隔离客户和目标对象,可以对目标对象进行功能扩展,目标对象和扩展功能职责清晰且没有耦合。
  • 静态代理在编译时就已经实现,代理关系在编译期就已经绑定,而动态代理是在运行时动态生成。
  • JDK动态代理面向接口代理,CGLIB动态代理可以通过继承实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值