Head First 设计原则和设计模式

  • 原则一:封装变化。找出应用中可能会变化之处,把它独立出来,不要和那些不需要变化的混在一起
  • 原则二:针对接口编程,而不是针对实现编程
  • 原则三:多用组合,少用继承
  • 策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
  • 观察者模式:定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
  • 原则四:为了交互对象之间的松耦合设计而努力。
  • 原则五:类应该对扩展开放,对修改关闭。
  • 装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
  • 工厂模式
    • 简单工厂模式
    • 工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类。
  • 依赖倒置原则:要依赖抽象,不要依赖具体类。
    • 抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
  • 单件模式:确保一个类只有一个实例,并提供一个全局访问点。
    • 多例模式
  • 命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤消的操作。
  • 适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
  • 外观模式:提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
  • 原则六:最少知识原则,只和你的密友谈话。(迪米特法则)
  • 模版方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
  • 原则七:好莱坞原则,别调用(打电话给)我们,我们会调用(打电话给)你。
  • 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
  • 原则八:单一责任(基本原则):一个类应该只有一个引起变化的原因。
  • 组合模式:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
  • 状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
  • 代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。

原则一:封装变化。找出应用中可能会变化之处,把它独立出来,不要和那些不需要变化的混在一起

封装变化,使其更具有弹性。eg:把鸭子中容易变化的fly()和quack()分别从duck类中独立出来

哪些因素导致你必须改变你的程序?

  • 我的客户或用户决定他们想要其他一些东西或新功能
  • 我公司决定使用另一个数据库,并且从使用不同数据格式的另一个支持商购买数据
  • 技术改变,所以我们不得不更新我们的代码取使用新的协议
  • 想要重构系统使其更好

原则二:针对接口编程,而不是针对实现编程

利用接口代表每个行为,行为的每个实现都要实现其中一个接口。这样,鸭子就不需要知道行为的具体实现了。
“针对接口编程”真正的意思是针对超类型编程,可以利用多态。多态:声明的变量类型应该时一个超类,通常是一个接口的抽象类,所以分配给这些变量的对象可以是父类的任何具体实现,这意味着类声明他们不需要知道具体对象类型。

原则三:多用组合,少用继承

“有一个”可能比“是一个”更好

策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。



什么时候使用策略模式?

  • 很多相关类仅仅行为不同
  • 你需要一个算法的不同变体
  • 算法使用的数据用户不应该知道(避免暴露内部数据结构)
  • 类定义了许多行为,这些行为在其操作中显示为多个条件语句

什么是设计模式:

  • 记录设计面向对象软件的经验作为设计模式
  • 每个设计模式系统性地命名,解释以及评估一个重要的且重复出现在面向对象系统中的设计
  • 目标是去捕获设计经验,用一种人们可以高效使用的形式
  • 每个模式描述了一个在我们的环境中反复出现的问题,然后描述了这个问题的解决方案的核心,这样您就可以将这个解决方案重复使用一百万次,而不必重复使用相同的方法两次

设计模式的好处:

  • 设计模式为你和其他开发者提供一个共享词汇表
  • 通过让你在模式层面思考,而不是基本对象层面,提升你关于架构的思考

如何使用设计模式?

  • 库和框架
  • DP帮助我们构建的程序更加可维护和可扩展
  • DP首先进入你的大脑

设计模式4元素:

  • 模式名:用一两个词描述一个设计问题,解决方案和结论
  • 问题:描述什么时候取应用模式
  • 解决方案:描述组成设计的元素,元素之间的关系,责任和合作
  • 结论:应用模式的结果和权衡

观察者模式:定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。


上图中,具体观察者持有主题的引用,为了在主题那注册成为观察者。

使用Java的Observable和Observer接口实现观察者模式
WeatherData.java

import java.util.Observable;
import java.util.Observer;
// 不再需要去持有observers,也不需要管理他们的注册和移除,父类Observable会处理他们
public class WeatherData extends Observable{privatate float temperature;privatate float humidity;privatate float pressure;public WeatherData() {  // 继承Observable之后不再需要创建保持Observers的数据结构}public void messurementsChanged() {setChanged();   // 在通知观察者之前先setChanged()来表明状态已经改变notifyObservers();  // 没有传数据对象,说明用的PULL模型}public void setMeasurements(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;messurementsChanged();}public float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}}

CurrentConditionsDisplay.java

import java.util.Observable;
import java.util.Observer;public class CurrentConditionsDisplay implements Observer, DispalyElement{Observable observable;private float temperature;private float humidity;public CurrentConditionsDisplay(Observable observable){this.observable = observable;   //构造器持有一个Observable,使用去添加当前对象为Observerobservable.addObserver(this);}public void update(Observable obs, Object arg){if(obs instamceof WeatherData){ // 确保Observable是WeatherData类型WeatherData weatherData = (WeatherData)obs;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}}public void dispaly(){System.out.println(temperature + humidity)}}

当两个对象之间松耦合,它们依然可以交互,但不清楚彼此的细节。观察者提供了一种对象设计,让主题和观察者之间松耦合。
任何时候都可以增加新观察者,因为主题唯一依赖的东西是实现Observer接口的对象列表。
改变主题或观察者其中一方,并不会影响另一方。

原则四:为了交互对象之间的松耦合设计而努力。

原则五:类应该对扩展开放,对修改关闭。

遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。需要把注意力集中在设计中最有可能改变的地方,然后应用开发-关闭原则。装饰者模式完全遵循开放-关闭原则。

装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。




装饰者类图

星巴兹实例类图

装饰者继承被装饰者而不使用组合?继承是因为装饰者和被装饰者必须是一样的类型,这里利用继承达到“类型匹配”,而不是利用继承获得“行为”。那么行为从哪来?当将装饰者与组件组合时,就是加入新行为,得到的新行为不是继承自超类,而是组合对象来的。

具体装饰者类


装饰者模式在JAVA中的应用


写一个 Lower Case Input Stream 类

public class LowerCaseInputStream extends FilterInputStream {public LowerCaseInputStream(InputStream in) {super(in);}public int read() throws IOException {int c = in.read();return (c == -1 ? c : Character.toLowerCase((char)c));}public int read(byte[] b, int offset, int len) throws IOException {int result = in.read(b, offset, len);for (int i = offset; i < offset+result; i++) {b[i] = (byte)Character.toLowerCase((char)b[i]);}return result;}
}

工厂模式

简单工厂模式

简单工厂把实例化代码放到工厂里,并不仅是转移问题到另一个对象(封装变化),因为简单工厂类可以有多个客户。
静态工厂不需要使用创建对象的方法来实例化对象,但缺点是不能通过继承来改变创建方法的行为。

简单工厂模式披萨店实例

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类。

通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
在超类中定义工厂方法来处理对象的创建,并将行为封装在子类中。这样,超类代码就和子类对象创建解耦了。

注意,子类决定要实例化的类是哪个,并不是指模式允许子类本身在运行时做决定,而是选择了使用哪个子类,自然就决定了实际创建的产品是什么。

工厂方法披萨店实例

  • 只有一个ConcreteCreator时,工厂方法模式优点: 帮助我们将产品的“实现”从“使用”中解耦,如果增加产品或改变产品的实现,Creator并不会受到影响。
  • 各自的ConcreteCreator看起来像利用简单工厂创建的? 用法不同。虽然每个具体商店实现看起来像简单工厂,但这里具体商店是扩展自一个类,此类有一个抽象方法createPizza(),由每个具体商店自行负责createPizza()方法的行为。而简单工厂中,工厂是另一个由Pizzasfore使用的对象。

依赖倒置原则:要依赖抽象,不要依赖具体类。

和“针对接口编程,不针对实现编程”很相似,但这里更强调抽象,这个原则说明:不能让高层组件依赖低层组件,而且,不管高层或低层组件,都应该依赖于抽象。

  • 应用工厂方法后,高层组件(PizzaStore)和低层组件(具体披萨)都依赖了Pizza抽象
  • 倒置思考方式:不要从上到下思考披萨店应该实现哪些披萨,而是从下往上思考各种披萨都是披萨,应该共享一个pizza接口。
  • 避免违反依赖倒置指导方针:
    1. 变量不可以持有具体类的引用:使用new就会持有具体类引用,可以改用工厂来避免;
    2. 不要让类派生自具体类:派生自具体类就会依赖具体类,应派生自接口。
    3. 不要覆盖基类中已实现的方法:如果覆盖,基类就不是真正适合被继承的抽象。基类中已实现的方法,应该由所有子类共享。

方针只是指导作用,如果类不容易改变,可直接实例化。

DIP的“倒置”在哪?

  • 依赖倒置原则中的“倒置”使因为它转换你典型地对OO设计的思维方式
  • 自上而下的依赖关系图会反转自身,高级和低级模块现在都依赖于抽象。
抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。


抽象工厂模式披萨店实例

注意到:抽象工厂的每个方法实际上看起来都像工厂方法。抽象工厂的方法经常以工厂方法的方式实现,接口内的每个方法负责创建一个具体产品,同时利用抽象工厂的子类来提供具体的做法。
工厂方法和抽象工厂:
1)都负责创建对象
2)工厂方法用继承,通过子类来创建对象,将客户从具体类型中解耦。
3)抽象工厂用组合,创建一个产品家族的抽象类型,其子类定义了产品被产生的方法,将客户从具体产品中解耦。
4)抽象工厂相比工厂方法,优点是可以把一群相关的产品集合起来,缺点是加入新产品就必须改变接口(扩展性较差)。
5)当需要创建产品家族和想让制造的相关产品集合起来时,使用抽象工厂
6)只是想把客户代码从需要实例化的具体类中解耦时,使用工厂方法

单件模式:确保一个类只有一个实例,并提供一个全局访问点。

多件?


在使用多线程后,单件模式可能出现问题,此时应该把getInstance()变成同步(synchronized)方法。

但每次都进行同步会造成性能损失,其实,只需要在第一次执行此方法时进行同步即可。
双重检查加锁

能不能继承单件类?
构造器私有,不能用私有构造器扩展类,必须把构造器改成公开的或受保护的,但这样就不能算单件了。

多例模式

所谓多例(Multiton Pattern)实际上就是单例模式的自然推广,属于对象创建类型的模式,多例模式其实就是限制了对象的数量,并且有可能对对象进行重复使用。
特点:

  1. 多例可以有多个实例
  2. 多例类必须能够自我创建并管理自己的实例,并且向外界提供自己的实例

多例类场景:
在java学习过程中,有一个池子的概念一直存在,好比作线程池,数据库连接池,这个池子是用来对线程,或者数据库连接对象进行管理的,第一,限制了池子中的对象数量,第二就是能够在使用过程中达到复用的效果,线程中的线程在执行完毕后,不会被直接回收掉,而会切换成等待状态,等待下一个任务提交,执行。数据库连接池也是如此,数据库操作在连接的时候,如果对数据库操作完毕后,会把资源释放,然后等待下一个数据库操作进行连接。这种设计其实是将对象的应用最大化了,避免了每次连接的时候都需要去创建一个对象。造成对象冗余或者内存升高。

命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤消的操作。

一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。命令对象将动作和接收者包进对象中,只暴露execute()方法,当该方法被调用时,接收者会进行这些动作。
从外面看,其他对象不知道究竟哪个接收者进行哪些动作,只知道调用execute()方法就能达到目的。
命令对象

命令模式类图


命令对象简单遥控的Client示例


**复杂遥控器(调用者)**调用者和接受者之间解耦

**NoCommand对象:**空对象,当你不想返回一个有意义的对象时,可以返回一个空对象。客户也可以将处理null的责任转移给空对象。
实现undo撤销功能


宏命令


接收者一定有必要存在吗?为何命令对象不直接实现execute()方法细节?
尽量设计“傻瓜”命令对象,它只懂得调用一个接收者的一个行为。让调用者和接收者之间解耦。
可以通过创建PartyCommand,在它的execute()方法调用其他命令,来实现Party模式吗?
这样相当于把Party模式“硬编程”到PartyCommand中。利用宏命令可以动态决定PartyCommand是由哪些命令组成,更灵活。
命令模式应用:队列请求(工作队列类和进行计算的对象之间解耦)、日志请求

Java语言使用命令模式实现AWT/Swing GUI的 委派事件模型 (Delegation Event Model, DEM)
在AWT/Swing中,Frame、Button等界面组件是请求发 送者,而AWT提供的事件监听器接口和事件适配器类是抽象命令接口,用户可以自己写抽象命令接口的子类来 实现事件处理,即实现具体命令类,而在具体命令类中 可以调用业务处理方法来实现该事件的处理。对于界面 组件而言,只需要了解命令接口即可,无须关心接口的实现,组件类并不关心实际操作,而操作由用户来实现。

适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

适配器模式火鸡冒充鸭子示例

万一系统中新旧并存,旧的部分期望旧接口,但我们已经使用新接口编写了这一部分,这是该怎么办?
可以创建一个双向适配器,支持两边的接口。想创建一个双向适配器,就必须实现所涉及的两个接口,这样可以当旧接口,或当新接口使用。

对象适配器和类适配器使用两种不同的适配方法(分别是组合与继承)。两个实现的差异如何影响适配器弹性?
对象适配实现接口,如果方法参数数量不同等,可能一些方法无法适配。
而类适配器使用继承,如果方法参数数量不同,可以通过改写父类方法来适配。
适配器模式的应用:将枚举器适配到迭代器


对于remove()方法,由于枚举不支持删除,因为枚举是一个“只读”接口,因此适配器无法实现一个有实际功能的remove()方法,最多只能抛出一个运行时异常UnsupportedOprationException。

外观模式:提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

外观包装子系统的类,那么需要低层功能的客户如何接触这些类?
外观没有“封装”子系统的类,只是提供简化的接口。外观一个很好特征:提供简化接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。
除了提供简化接口,外观模式还允许将客户实现从任何子系统中解耦。
**适配器模式和外观模式区别:**外观和适配器都可以包装许多类。但适配器的意图是“改变”接口符合客户的期望,外观模式的意图是提供子系统的一个简化接口。
外观模式的家庭影院实例

原则六:最少知识原则,只和你的密友谈话。(迪米特法则)

对任何对象,在该对象的方法内,只应该调用以下范围方法:

  • 该对象本身
  • 被当做方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件

    **最少知识原则缺点:**导致更多“包装”类被制造出来,以处理和其他组件的沟通,导致复杂度和开发时间增加,并降低运行时性能。

模版方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。



hook钩子

钩子被声明在抽象类中,但只有空的或默认的实现。可以让子类有能力对算法的不同点进行挂钩。子类可以自行决定要不要覆盖钩子方法,如果不覆盖,抽象类会提供一个默认的实现。

创建模版方法时,什么时候使用抽象方法,什么时候使用钩子?

  • 当子类必须提供算法中某个方法或步骤的实现时,就用抽象方法;
  • 如果算法的这个部分时可选的,就用钩子。

原则七:好莱坞原则,别调用(打电话给)我们,我们会调用(打电话给)你。

允许低层组件将自己挂钩到系统上,但高层组件会决定什么时候和这样使用这些低层组件。
好莱坞原则和模板方法

好莱坞原则和依赖倒置原则的关系:

  • 依赖倒置原则教我们尽量避免使用具体类,而多使用抽象,更注重在设计中避免依赖。
  • 好莱坞原则是在创建框架或组件上的一种技巧,好让低层组件能被挂钩进计算中,而不会让高层组件依赖低层组件。
    工厂方法是模板方法的特例

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。


迭代器模式的餐厅菜单示例


这个迭代器让女招待员从具体类的实现中解耦,她不需要知道菜单是使用数组还是ArrayList,只关心她能够取得迭代器。

原则八:单一责任(基本原则):一个类应该只有一个引起变化的原因。

组合模式:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。


组合模式类图

组合菜单

状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。


状态模式的类图几乎跟策略模式的类图一样,但两个模式的差别在于它们的意图
状态模式将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。
策略模式中客户通常主动指定context所要组合的策略对象是哪个。
状态模式糖果机具体状态类示例

糖果机类(Context)

当状态转换是固定的时候,状态决定适合放在Context中,当转换是动态时,状态决定就放在状态类中。

代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。

Java RMI: RMI提供客户辅助对象和服务辅助对象,为客户辅助对象创建和服务对象相同的方法,好处是客户不必亲自写任何网络或I/O代码。
RMI将客户辅助对象成为stub(桩) ,服务辅助对象成为skeleton(骨架)

制作远程服务的五个步骤:



客户如何取stub对象

代理模式糖果机示例


虚拟代理

显示CD封面示例
创建一个Icon接口从网络上加载图像,在加载未完成时显示“CD封面加载中,请稍后…”,一旦加载完成,代理就把显示的职责委托给Icon。

笔记:Head First设计原则和设计模式相关推荐

  1. wordvba编程代码大全_面向对象、设计原则、设计模式、编程规范、重构

    面向对象.设计原则.设计模式.编程规范.重构 面向对象 主流的三个编程风格有:面向对象,面向过程,函数式编程. 面向对象是最主流的风格,面向对象具有丰富的特性(封装,抽象,继承,多态). 面向对象 面 ...

  2. 面向对象编程,设计原则,设计模式

    2019独角兽企业重金招聘Python工程师标准>>> 面向对象编程,设计原则,设计模式 面向对象编程 面向对象编程与面向过程编程的区别 面向对象软件开发的优点 面向对象编程语言 C ...

  3. 类设计原则及设计模式(一篇就够)

    类设计原则及设计模式 类设计的六大原则 设计模式定义 设计模式的分类 创建型模式 1. 简单工厂和工厂方法模式 定义和分类 2. 抽象工厂模式 3. 单例模式 定义 优缺点 饿汉式单例与懒汉式单例类比 ...

  4. Python、设计原则和设计模式-创建类设计模式

    Python.设计原则和设计模式 前言 程序的目标:高内聚 低耦合 有哪些设计原则 设计原则是「高内聚.低耦合」的具体落地. 单一职责原则要求在软件系统开发.设计中,一个类只负责一个功能领域的相关职责 ...

  5. 提升代码质量的方法:领域模型、设计原则、设计模式

    点击上方"服务端思维",选择"设为星标" 回复"669"获取独家整理的精选资料集 回复"加群"加入全国服务端高端社群「后 ...

  6. 软件的设计原则,设计模式以及软件的质量属性

    文章目录 软件的设计 设计原则 设计模式 软件的质量属性 定义 关系 软件的设计 ant design 设计价值观 在蚂蚁的开源项目ant design的介绍中,谈到了价值观,原则和模式之间的关系: ...

  7. 《Head.First设计模式读书笔记》之设计原则一、二和三

    ü  设计原则1:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起. 例如:对于一个鸭子类,不同的鸭子(真鸭子,木头鸭子等)有不同的飞行行为,呱呱叫行为,所以要把这两个行 ...

  8. 七大设计原则与设计模式(创建型模式、结构型模式、行为型模式)

    七大设计原则 开闭原则.依赖倒置原则.单一职责原则.接口隔离原则.迪米特法则(最少知道原则).里氏替换原则.合成 (组合).聚合复用原则 开闭原则 定义: 一个软件实体如类.模块和函数应该对扩展开放, ...

  9. 软件设计原则及设计模式

    一. 软件六大设计原则(SOLID) Single Responsibility Principle:单一职责原则 Open Closed Principle:开闭原则 Liskov Substitu ...

最新文章

  1. Android数据存储(三)——SQLite
  2. 复地集团的现代化办公方案
  3. 交换安全老师课堂笔记
  4. java打印版本兼容_打印class文件的Java编译器内部的版本号
  5. spring mvc DispatcherServlet详解之二---request通过Controller获取ModelAndView过程
  6. 详细介绍阿里云搭建RocketMq
  7. Oracle12C 怎样导入scott用户
  8. BI与大数据之间的差距有哪些
  9. 数据结构:线性表的顺序存储结构,实现集合的交差并补
  10. 机器学习应用——强化学习课程总结 实例 “自主学习Flappy Bird游戏”(MDP蒙特卡洛强化学习Q-learningDRLDQN)
  11. 【Try to Hack】veil-evasion免杀
  12. 三 国外IP核主要竞争对手
  13. php7 memcached sasl,memcached sasl
  14. N-vop、S-vop、Packed Bistream
  15. 2019京东618活动提报要求一览
  16. 【Ant Design】下拉列表Select 、日期选择框DatePicker等跟随滚动条上下移动解决方案
  17. 「迅捷」校园网多设备认证
  18. orcal-day09-kattle之Spoon-oracle完成数据清洗
  19. 【Java】一次BugFix的思路
  20. 转载:Bjarne Stroustrup百科

热门文章

  1. spring boot 源码解析8-SpringApplication#run第8步
  2. MVCC 水略深,但是弄懂了真的好爽
  3. 类的成员:构造器(构造方法)
  4. Java方法的参数传递解析
  5. Spring 一览众山小
  6. 《Java8实战》读书笔记10:组合式异步编程 CompletableFuture
  7. 计算机网络-将C网192.168.25.0划分四个子网,计算每个子网的有效IP地址范围和对应网络掩码
  8. windows下命令行解压zip文件
  9. 关于多张图片在数据库中的存储问题
  10. 网易云音乐突破红海的思考