Java - 核心设计模式和原则

(本次设计模式摘自大话设计模式书本知识,用 C# 讲解的)

简单工厂模式

  • 简单工厂模式是属于创建型模式,简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
    在这里插入图片描述

策略模式

  • 前言:面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
  • 策略模式: 它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
  • 策略模式结构图:
    在这里插入图片描述
    对于这个 Context 存在的意义:
    在这里插入图片描述
    客户端可通过 Context 来实现想要实现的方法。
  • 策略模式解析:
  1. 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少各种算法类使用之间的耦合。
  2. 该 Strategy 类层次为 Context 定义了一系列的可供重用的算法或行为,继承有助于析取出这些算法的公共功能。
  3. 策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
  4. 策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

单一职责原则

  • 单一职责原则就一个类而言,应该仅有一个引起它变化的原因。
  • **注意:**如果一个类承担的指责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力,这种耦合会导致脆弱的设计,当变化发生时,设计会遭受意想不到的破坏。
  • 软件设计真正要做的许多内容,就是发现职责并把这些职责相互分离。

开放-封闭原则

  • 开放封闭原则是说软件实体(类,模块,函数等等)应该可以扩展,但是不可以修改。
  • 及:对于扩展是开放的,对于更改是封闭的。
  • 无论模块多么的“封闭”,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最优可能发生的变化种类,然后构造抽象来隔离这些变化。
  • 当我们最初编写代码时,假设变化不会发生。当变化发生时。我们就创建抽象来隔离以后发生的同类变化。
  • 面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。
    在这里插入图片描述
  • 开放封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。切记,切记。

依赖倒置原则

  • 依赖倒转原则
    A.高层模块不应该依赖低层模块。两个都应该依赖抽象
    B.抽象不应该依赖细节。细节应该依赖抽象。
  • 说白了,就是针对接口编程,不要对实现编程。

里氏替换原则

  • 里氏代换原则(LSP):子类型必须能够替换掉它们的父类型。
  • 也正因为有了这个原则,使得继承复用成为了可能,只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
  • 依赖倒转其实就是谁也不要依靠谁,除了约定的接口,大家都可以灵活自如。
    在这里插入图片描述

迪米特法则

  • “迪米特法则(LoD)’ 也叫最少知识原则。
  • 迪米特法则(LoD),如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
  • 迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限。
  • 迪米特法则其根本思想,是强调了类之间的松耦合
  • 类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。

装饰模式

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

  • 装饰模式结构图:
    在这里插入图片描述
    Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent 是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator, 装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能。”

  • 总结:装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。这样做更大的好处就是有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。


代理模式

  • 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问
  • 代理模式结构图:
    在这里插入图片描述
  • 代理模式应用
  1. 远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
  2. 虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化。
  3. 安全代理,用来控制真实对象访问时的权限。
  4. 智能指引,是指当调用真实的对象时,代理处理另外一些事。

观察者模式

  • 观察者模式又叫做发布-订阅(Publish/Subscribe) 模式。
  • 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,
    会通知所有观察者对象,使它们能够自动更新自己。
  • 简单样例图:
    在这里插入图片描述
  • 观察者模式结构图:
    在这里插入图片描述
    解析:
  1. Subject类,可翻译为主题或抽象通知者, 一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  2. Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。
  3. ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体现察者对象;.在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
  4. ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
  • 观察者模式特点
  1. 将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。 我们不希望为了维持一致性而使各类紧密耦合, 这样会给维护、扩展和重用都带来不便。
  2. 当一个对象的改变需要同时改变其他对象的时候 ,而且它不知道具体有多少对象有待改变,应该考虑使用观察者模式
  3. 当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
  • 观察者模式的不足
    尽管已经用了依赖倒转原则,但是‘抽象通知者’还是依赖‘抽象 观察者’,也就是说,万一没有 了抽象观察者这样的接口,我这通知的功能就完不成了。另外就是每个具体观察者,它不一定是‘更新’的方法要调用呀,就像我希望的是‘工具箱’是隐藏,‘自动窗口’
    是打开,这根本就不是同名的方法。这应该就是不足的地方吧。”

解决方案 -->> 事件委托实现

  • “抽象通知者”由于不希望依赖“抽象观察者”,所以“增加”和“减少”的方法也就没有必要了(抽象观察者已经不存在了)。
  • 事件委托说明
  1. 委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。
  2. 一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。
  3. 委托也是有前提的,那就是委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。

抽象工厂模式

前言:

  • 用了工厂方法模式的数据访问程序
    在这里插入图片描述
  • 用了抽象工厂模式的数据访问程序
    在这里插入图片描述
  • 抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 抽象工厂模式结构图:
    在这里插入图片描述
  • “AbstractProductA 和 AbstractProductB是两个抽象产品,之所以为抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是User和Department,而 ProductA1、ProductA2 和ProductB1,ProductB2就是对两个抽象产品的具体分类的实现,比如ProductA1可以理解为是SqlserverUser,而ProductB I是AccessUser。
  • 这么说,IFactory 是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2就是具体的工厂了。就像SqlserverFactory和AccessFactory一样。通常是在运行时刻再创建一个 ConcreteFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。”
  • 抽象工厂模式的优点与缺点
  1. 最大的好处便是易于交换产品系列,由于具体工厂类,例如 IFactory factory = new AccessFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
  2. 我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小,现在如果你要更改数据库访问,我们只需要更改具体工厂就可以做到。第二大好处是,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
  3. 是个模式都是会有缺点的,都有不适用的时候,要辨证地看待问题哦。抽象工厂模式可以很方便地切换两个数据库访问的代码,但是如果你的需求来自增加功能,比如我们现在要增加项目表Project,那就至少要增加三个类,IProject、 SqIserverProject、 AccessProject, 还需要更改 IFactory、SqlserverFactory和 AccessFactory才可以完全实现。啊,要改三个类,这太糟糕了。
  • 编程是门艺术,大批量的改动,显然是非常丑陋的做法。

用简单工厂来改进抽象工厂

  • 结构图:
    在这里插入图片描述
  • 抛弃了IFactory、 SqlserverFactory 和AccessFactory三个工厂类,取而代之的是DataAccess 类,由于事先设置了db的值(Sqlserver或Access),所以简单工厂的方法都不需要输入参数,这样在客户端就只需要 DatAccess.CreateUser和DataAccess.CreateDepartment()来生成具体的数据库访问类实例,客户端没有出现任何一个SQL Server或Access 的字样,达到了解耦的目的。

用反射+抽象工厂的数据访问程序

  • 结构图:
    在这里插入图片描述

用反射+配置文件实现数据访问程序

  • 添加一个 config 文件:
    在这里插入图片描述
  • 现在我们应用了反射+抽象工厂模式解决了数据库访问时的可维护、可扩展的问题。从这个角度上说,所有在用简单工厂的地方,都可以考虑用反射技术来去除 switch 或 if,解除分支判断带来的耦合

适配器模式

  • 适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一
    起工作的那些类可以一起工作。
  • 在软件开发中,也就是系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。
  • 适配器模式(Adapter)结构图:
    在这里插入图片描述
何时使用适配器模式?
  • 两个类所做的事情相同或相似,但是具有不同的接口时要使用它。客户代码可以统一调用同一接口就行了,这样可以更简单、更直接、更紧凑。
  • 并且,要在双方都不太容易修改的时候再使用适配器模式适配。
  • 样例结构图:
    在这里插入图片描述
    尽管外籍球员曾经是不太懂英文,尽管教练和球员也不会学中文,但因为有了翻译者, 团队沟通合作成为了可能。
  • 需要注意的是
    如果能事先预防接口不同的问题,不匹配问题就不会发生;在有小的接口不统一问题发生时,及时重构,问题不至于扩大;只有碰到无法改变原有设计和代码的情况时,才考虑适配。事后控制不如事中控制,事中控制不如事前控制

单例模式

  • 单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
  • 单例模式(Singleton)结构图:
    在这里插入图片描述
  • 好处还有:比如单例模式因为Singleton 类封装它的唯一实例, 这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。
  • 多线程访问单例的时候,可以用 lock 解决可能造成创建多个实例的问题。或者,利用静态初始化方法:
    在这里插入图片描述
  • 由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例类,原先的单例模式处理方式是要在第一次被引用时, 才会将自己实例化,所以就被称为懒汉式单例类
  • 单例模式的实现方式还有很多很多种, 饿汉式单例和懒汉式单例只是两种比较主流和常用的单例模式方法。,懒汉式的单例模式是线程可能会有安全隐患。

……未完待续,持续更新

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章