关于 Java 设计模式的学习

继承

  1. 两个类之间是 is-a 的关系可以考虑用继承
  2. 而两个类之间是 has-a,表示某个角色具有某一项责任,不适合用继承

例如:飞机场有飞机,但是飞机不能去继承飞机场

多态

多态表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行

特点:(子替父演戏实例)

  1. 子类以父类身份出现
  2. 子类在工作时以自己的方式实现
  3. 子类以父类身份出现时,子类特有的属性和方法不可以使用

多态原理

当方法被调用时,无论对象是否被转换成其父类,都只有位于对象继承最末端的方法实现会被调用。 即父类的抽象方法是按照其运行时类型而非编译时类型进行动态绑定调用的

抽象类

  • 不能实例化
  • 抽象方法必须被子类重写
  • 若类包含抽象方法,则类必须定义为抽象类

总结:抽象类代表一个抽象概念,它提供一个继承的出发点,当设计一个新的抽象类时,一定是用来继承的

在一个继承关系树里,树叶节点应为具体类,树枝节点均应为抽象类。

例如:动物是抽象类,猫是具体类,而当猫划分品种时,比如包含金丝猫和其他品种,那猫应该是树枝,也应是抽象类。

接口

接口是把隐式公共方法和属性组合起来,以封装特定功能的一个集合。

一旦类实现了接口,类就可以支持接口所指定的所有属性和成员。

声明接口在语法上与声明抽象类完全相同,但不允许提供接口中任何成员的执行方式(没有具体实现)

所以接口不能实例化、不能有构造方法字段、不能有修饰符、不能声明为抽象的静态的

实现接口的类必须实现接口中所有方法和属性

区分抽象类和接口

抽象

  1. 类是对对象的抽象

  2. 抽象类是对类的抽象

  3. 接口是对行为的抽象

对象是否有相似关联

  1. 若行为跨越不同类的对象,可使用接口

  2. 对于相似的类对象,用继承抽象类

设计角度

  1. 抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类

  2. 接口根本不知道子类的存在,方法如何实现不确认,预先定义

通过重构改善既有代码的设计 所有抽象类往往通过重构得来

面向对象设计(OOD)

  1. 可维护

  2. 可复用

  3. 可拓展

  4. 灵活性好

UML

关联

聚合(弱拥有):A 对象可以包含 B 对象,但 B 对象不是 A 对象的一部分

组合(强拥有):严格的整体与部分的关系

依赖

简单工厂

用单独的类考虑创造实例的过程

策略模式

官方定义

它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

解释

所有的算法完成相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

优势

另外策略模式简化了单元测试,因为每个算法有独立的类,可以通过自己的接口单独测试。

使用场景

当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。将这些行为进行封装在一个个独立的 Strategy 类中,就可以在使用这些行为的类中消除条件语句。

策略模式结合简单工厂模式

在基本策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的对象。

当策略与简单工厂结合,选择的具体实现也可以由 Context 来承担,最大化减轻客户端职责。

单一职责

官方定义

就一个类而言,应该仅有一个引起它变化的原因。

背景

若一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。

这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

实例

手机承担多职责

谷歌将一切需求整合到一个文本框

开放-封闭原则(The Open-Closed Principle,开闭原则 OCP)

实例

不能修改,但可以扩展的一国两制

特点

Open for extension 对扩展开放

Closed for modification 对更改封闭

实例

早到早下班,晚到晚下班,对业绩成效修改关闭,对时间制度拓展开放的弹性工作制

依赖-倒转原则

官方定义

抽象不应该依赖细节,细节应该依赖于抽象。

解释

针对接口编程,不针对实现编程。

原则

  1. 高层模块不应该依赖低层模块,两个都应该依赖抽象

  2. 抽象不应该依赖细节,细节应该依赖抽象

里氏代换原则

解释

一个软件实体如果使用的是一个父类,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。

即把父类替换成其子类,程序的行为没有变化。

简言之

子类型必须能够替换掉它们的父类型

依赖倒转与面向对象

依赖倒转是面向对象设计的标志,若编写程序时考虑如何针对抽象编程而不是针对细节编程。

即程序中所有依赖关系都终止于抽象类或者接口,即是面向对象设计,反之则是过程化设计。

装饰模式(Decorator)

实例

Avatar 系统(个人形象系统)

官方定义

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

使用场景

把每个要装饰的功能放在单独的类,并用这个类包装要修饰的对象。

可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象。

优势

  1. 把类中的装饰功能从类中移除,简化原有类

  2. 有效把类的核心职责和装饰功能区分开,去除相关类中重复的装饰逻辑

代理模式

GOF 解释

为其他对象提供一种代理以控制对这个对象的访问

应用场景

  1. 远程代理,为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。

  2. 虚拟代理,根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。(达到性能最优化)

  3. 安全代理,用来控制真实对象访问时的权限

  4. 智能指引,当调用真实的对象时,代理处理另外一些事

    例如:

    • 计算真实对象的引用次数,当该对象没有引用时可以自动释放它

    • 当第一次引用一个持久化对象时,将它装入内存

    • 在访问一个实际对象前,检查是否已经锁定它,以确保其他对象不能改变它。

    以上都是通过代理在访问一个对象时附加一些内务处理

工厂方法模式

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工方法使一个类的实例化延迟到其子类。

与简单工厂的关系

  1. 实现对修改关闭,对拓展开放,符合开闭原则
  2. 依赖倒置,依赖抽象而非依赖实现

实现特点

工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类。

工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。

原型模式

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

解释

从一个对象再创建另一个可定制的对象,而且不需知道任何创建的细节

使用场景

一般在初始化的信息不发生变化的情况下,克隆是最好的办法。

既隐藏了对象创建的细节,又对性能大大提高(省去调用构造方法的时间)

优势

不用重新初始化对象,而是动态地获得对象运行时的状态

浅复制和深复制

浅复制:被复制对象的所有变量都含有与原来对象相同的值,而所有对其他对象的引用都仍然指向原来的对象。

深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

模板方法

使用场景

既然使用继承,并且肯定这个继承有意义,就应该成为子类的模板,所有重复的代码都应该上升到父类,而非每个子类都去重复。

当要完成在某一个细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,通常考虑用模板方法处理。

官方定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

迪米特法则(最少知道原则)

官方定义

如果两个类不必彼此通信,那么这两个类就不应单发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

解释

迪米特法则首先强调的前提是类的结构设计上,每一个类都应当尽量降低成员的访问权限。

即一个类包装好自己的私有状态,不需要让别的类知道的字段或行为就不要公开。

类之间的耦合越弱,越有利于复用。一个处在若耦合的类被修改,不会对有关系的类造成波及。

实例

公司办事找负责人

外观模式 Facade(门面模式)

官方定义

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

特点

完美体现依赖倒转原则和迪米特法则

使用场景

  1. 设计初期,有意识地将不同的两个层分离

  2. 开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观 Facade 可以提供一个简单的接口,减少它们之间的依赖

  3. 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了。为新系统开发一个外观 Facade 类,来提供设计粗糙或高度复杂的遗留代码的比较清晰的接口,让新系统与 Facade 对象交互,Facade 与遗留代码交互所有复杂的工作。

建造者模式(生成器模式)

讲一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

使用场景

当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方法时适用的模式。

观察者模式(发布-订阅模式,Publish/Subscribe)

定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

背景

将一个系统分隔成一系列相互协作的类有一个很不好的副作用,就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、拓展和重用都带来不便。

使用场景

  1. 当一个对象的改变需要同时改变其他对象,而且不知道具体有多少对象有待改变。

  2. 一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

委托方法

解释

委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。

委托方法的使用可以像其他任何方法一样,具有参数和返回值。

委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。