《大话设计模式》学习笔记
睡醒后就拿起《大话设计模式》这本书来看。之前对于设计模式只是一知半解,看了之后感觉受益匪浅。
在此做个小笔记。也强烈建议像我这样的萌新去看看。(很重要、很重要、很重要...)
前言
首先复习一下向对象编程的优点:可维护,可复用,可扩展,灵活性好。
面向对象的好处:(原文)
之后当我学习了面向对象的分析设计编程思想,开始考虑通过封装、继承、多态把程序的耦合度降低,传统印刷术的问题就在于
所有字都刻在同一版面上造成耦合度太高太高所致,开始同设计模式使得程序更加的灵活,容易修改,并且易于复用。
(1).<简单工厂模式>(书本例子:计算器的实现)
主要是利用继承的特性,
父类对象的引用指向一个子类的对象。
工厂根据输入要求,实例化出合适的子类对象返回给父类。
public class AnimalFactory { public static Animal getAnimal(String name) {
Animal anima = null; if(name.equals("cat")) {//选择判断的逻辑 anima = new Cat();//继承Animal类 } else { anima = new Dog();//继承Animal类
}
return anima;
}
//使用AnimalFactory工厂获取实例对象 Animal anima = AnimalFactory.getAnimal("cat"); //调用anima的方法
anima.yell();
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,接触分支判断带来的耦合。
(2).<策略模式Strategy>(书本例子:商场收银软件)
定义:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
策略上下文 类
public class StrategyContext { private Method method; public StrategyContext(Method method) {//抽象策略类 this.method = method; } public String getTotle() { method.getTotleMoney();//不同的实现类有不同的算法 } }
客户端调用
StrategyContext methodContext = null; if(?) { methodContext = new StrategyContext(new CashNormal());//CashNormal继承了Method抽象策略类 } else { methodContext = new StrategyContext(new CashRebate());//CashRebate继承了Method抽象策略类 } animaContext.getTotleMoney();//算法
策略模式经常与简单工厂模式一起使用
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是具体实现不同,他可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合
策略模式封装了变化
(3).<装饰模式Decorator>(书本例子:怎么穿衣打扮)
定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
类似与一个对象调用链,类似于拦截器的原理,自外而内的调用循序。
所有Decorator类都继承同一个类。
public class PersonA extends Person{ private Person component; public void decorate(Person component) { this.component = component; } @Override public void show() { System.out.println("PersonA: 在装饰类的show方法多加一句打印"); component.show(); } }
Person p1 = new Person(); PersonA p2 = new PersonA(); PersonB p3 = new PersonB(); p2.decorate(p1); p3.decorate(p2); p3.show();
(4).<代理模式Proxy>(书本例子:替别人送情书)
定义:为其他对象提供一种代理以控制对这个对象的访问 。
代理类 和 被代理类 实现同一个主题接口,
代理类中包含被代理类的实例,其他类调用代理类的代理方法
public class PersonA extends Person{ private PersonB personB; @Override public void proxy() { if(personB == null) { personB = new PersonB(); } personB.proxy(); } }
应用场景:
1.远程代理,也就是为一个对象在不同的地址空间提供局部代理。这样可以隐藏一个对象存在于不同地址空间的事实。
2.虚拟代理,根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
3.安全代理,用来控制真实对象访问时的权限,一般用于对象应该有不同的访问权限的时候。
4.智能指引,是指当调用真实的对象时,代理处理另外一些事。
(5).<工厂方法模式>(书本例子:学雷锋做好事)
定义一个用于创建对象的接口,让子类决定实例化哪一个类的实例延迟到其子类。
工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现雷锋类,选择判断的问题交给客户端。
(对比简单工厂)这时如果要在原来的选择分支上加一个选择分支,不需要在原工厂实体类总增加一个分支判断,只需要增加一个实现工厂接口的类,一个继承‘雷锋’类的实体类。(开放-封闭原则)
看看简单小小例子
public interface LeiFeng {//雷锋精神接口 //......具体方法 public void help();
}
public class Undergraduate implements LeiFeng {//大学生有雷锋精神 @Override public void help() { } }
public class Volunteer implements LeiFeng {//志愿者有雷锋精神 @Override public void help() { } }
public interface LeiFengFactory {//雷锋工厂接口 public LeiFeng createLeiFeng();//生产雷锋 }
public class UndergraduateFactory implements LeiFengFactory {//大学生雷锋工厂 @Override public LeiFeng createLeiFeng() { return new Undergraduate();//生成大学生雷锋 } } public class VolunteerFactory implements LeiFengFactory {//大学生雷锋工厂 @Override public LeiFeng createLeiFeng() { return new Volunteer();//生成大学生雷锋 } }
LeFengFactory lefengFactory = new VolunteerFactory()//只需要变更雷锋工厂即可 LeFeng lefeng = lefengFactory.createLeiFeng();//创建雷锋
(6).<原型模式Prototype>(书本例子:复印简历)
定义:用原型实例指定创建对象的种类并且通过拷贝这些原创创建新的对象。
原型模式实际上就是实现Cloneable接口,重写clone()方法。
一般在初始化的信息不发生变化的情况下,可容是最好的办法。这既隐藏了对象创建的细节,对于性能也是大大的提高。不用重新初始化对象,二十动态的获得对象运行时的状态。
克隆注意:克隆时如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制对象的引用的对象:因此,原对象和其复本引用同一对象。因此加入浅复制与深复制思想
深复制就是在原克隆基础上,如果对象是引用类型,不再复制其引用地址,而直接复制引用类型的复制(此时的引用类型需要实现也实现Cloneable接口,重写clone()方法)
(7).<模版方法模式Template Method>(书本例子:考试试卷,考题一样,答案不一样)
定义:定义一个操作中的算法的骨架,而将一些步骤延迟的子类中。模版方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
当我么要完成在某一细节层次一致的·一个过程或者一些列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常考虑用模版方法模式来处理。
abstract class AbstractClass { public abstract void primitiveOperation1();//抽象行为,放到子类去实现 public abstract void primitiveOperation2();//抽象行为,放到子类去实现 public void TemplateMethod() { //模版方法,给出了逻辑的骨架 //这里可以方一些共用处理
primitiveOperation1(); //逻辑的组成是一些相应的抽象操作,而且都推迟到子类去实现 //这里可以放一些共用处理
primitiveOperation2(); } }
模版方法模式是通过把不变的行为搬移到超类,去除子类中的重复代码来体现它的优势。是一个很好的代码复用平台
(8).<外观模式Facade>(书本例子:炒股和买基金(基金帮你炒股))
定义:为子系统中的一组接口提供一个一致的界面,此模式定义一个高层接口,这个接口使得这一子系统更加容易使用。减少依赖
首先:在设计初期阶段,应该有意识的将不同的两层分离。
其次:在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,
第三:在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,为新系统开发一个Facade类,来提供设计粗糙或者高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
(9).<建造者模式Builder>(书本例子:传统菜馆和肯德基流水生产)
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
类似与模版方法模式,只是把逻辑骨架的判断放到了一个指挥者类来控制。
public class Director { public void construct(AbstractClass AbstractClass) {//逻辑骨架 //这里可以方一些共用处理 AbstractClass.primitiveOperation1(); //逻辑的组成是一些相应的抽象操作,而且都推迟到子类去实现 //这里可以方一些共用处理 AbstractClass.primitiveOperation2(); } }
建造者模式实在当创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式时使用此模式。
(10).<观察者模式Observe>(书本例子:老板回公司,员工不知道怎么办)
定义:定义了一种一对多(多对多?)的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发送变化时,会通知所以观察这对象,使他们能够自动更新自己。
主题类:
public abstract class Subject { //定义一个观察者数组 private Vector<Observer> obsVector = new Vector<Observer>(); //增加一个观察者 public void addObserver(Observer o){ this.obsVector.add(o); } //删除一个观察者 public void delObserver(Observer o){ this.obsVector.remove(o); } //通知所有观察者 public void notifyObservers(){ for(Observer o:this.obsVector){ o.update(); } } }
当一个对象的改变需要同事改变其他对象时,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
观察者和主题可以相互依赖,保留对方的接口。
观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。
扩展思考:微博中所以用户可以即使观察者也是主题。
(11).<抽象工厂模式Abstract Factory>(书本例子:换DB)
定义:提供一个创建一系列相关或相互依赖的接口,而无需指定它们具体的类。
判断分支时,经常与反射+配置文件一起食用哟。
(是不是发现IOC的影子了 嘻嘻)
(12).<状态模式State>(书本例子:程序员加班时的状态)
定义:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其他类。
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。
类似与一个状态执行链(有序,调用下一状态),替代条件分支判断语句。
主要包含下面三个部分。
1.要有一个状态接口或状态抽象类,需要接受上下文环境信息。
2.实现状态接口,主要有两个实现,a判断上下文环境,若符合状态信息执行对应的操作。b若不符合状态信息,调用下一个状态的判断。(知道下一个状态链)
3.上下文环境信息类。对外开放,负责状态的切换。
思考:状态模式和装饰模式的差异?
(13).<适配器模式Adapter>(书本例子:姚明打NBA需要翻译,电源适配器)
定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
系统的数据和行为都正确,但接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。
适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求一致的情况。
适配器类的要求,1.实现上层接口,2.存有需转换的对象,3.接口的实现方法中使用转换对象的行为来代替。
思考:适配器模式和代理模式的差异?
(14).<备忘录模式Memento>(书本例子:打游戏时存档)
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
大致有三个类:
1.发起人Originator,备忘录信息的提供者,负责创建备忘录。
2.备忘录Memento,单纯的数据对象,存储发起人的信息,信息可以被提取。
3.备忘录管理者Caretaker,可存储备忘录对象,提供备忘录对象,发起人创建的备忘录信息由它统一管理。
memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,可以提供回滚rollback功能。事物管理的时候就是这样实现。
使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。
下面从别的地方找来一个适合备忘录类使用的工具,利用反射原理来保存、提取备忘录数据
public class BeanUtils { //把bean的所有属性及数值放入到Hashmap中 public static HashMap<String,Object> backupProp(Object bean){ HashMap<String,Object> result = new HashMap<String,Object>(); try { //获得Bean描述 BeanInfo beanInfo=Introspector.getBeanInfo(bean.getClass()); //获得属性描述 PropertyDescriptor[] descriptors=beanInfo.getPropertyDescriptors(); //遍历所有属性 for(PropertyDescriptor des:descriptors){ //属性名称 String fieldName = des.getName(); //读取属性的方法 Method getter = des.getReadMethod(); //读取属性值 Object fieldValue=getter.invoke(bean,new Object[]{}); if(!fieldName.equalsIgnoreCase("class")){ result.put(fieldName, fieldValue); } } } catch (Exception e) { //异常处理 } return result; } //把HashMap的值返回到bean中 public static void restoreProp(Object bean,HashMap<String,Object> propMap){ try { //获得Bean描述 BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); //获得属性描述 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); //遍历所有属性 for(PropertyDescriptor des:descriptors){ //属性名称 String fieldName = des.getName(); //如果有这个属性 if(propMap.containsKey(fieldName)){ //写属性的方法 Method setter = des.getWriteMethod(); setter.invoke(bean, new Object[]{propMap.get(fieldName)}); } } } catch (Exception e) { //异常处理 System.out.println("shit"); e.printStackTrace(); } } }
(15).<组合模式Composite>(书本例子:公司部门与分公司的关系,树结构)
定义: 将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
为了实现整体与部分一致对待,有利于以后的扩展或修改。
1.抽象零件类Component,定义了部件的参数和方法,可以增加零件、减少零件
2.叶子节点对象Leaf,继承抽象零件类,因为是叶子节点,所以不实现增加零件、减少零件的具体实现。
3.树枝零件Composite,继承抽象零件类,包含零件类的数组
引申出几个问题。因为叶子节点和树枝节点的区别,所以零件类定义的之后,是否需要定义增加零件、减少零件的方法呢?
由叶子节点重写方法但没有具体实现(透明方式),还是树枝节点单独增加自己的增加零件、减少零件的方法(安全方式)?
什么时候用到组合模式:需求中是体现部分与整体层次的结构时,只要是树形结构就可以考虑使用组合模式。
(16).<迭代器模式Iterator>(书本例子:上车后售票员找乘客买票)
定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表达。例如java中的collection与iterator.(此模式已经被撤销,但仍然可以研究)
1.迭代器抽象类 :为了遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一接口。
2.迭代器具体实现类 :含有容器抽象类(需要实体化),对容器抽象类进行遍历操作
3.容器抽象类 :必须要有创建迭代器的方法。createIterator();
4.容器具体实现类 :实现接口,创建一个迭代器,让迭代器持有自己的引用。
当你需要对聚集有多种方式遍历时,可以考虑用迭代器模式。
(17).<单例模式Singleton>(书本例子:有些类也需要计划生育)
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
直接看代码
package com.ww; public class Singleton { private final static Singleton singleton = new Singleton(); //新建一个私有的静态的Singleton类 private Singleton() { //私有的无参构造方法,出自己之外无法初始化 } public static Singleton getSingleton() { //共用静态方法 获得单例对象 return singleton; } }
package com.ww; public class Singleton { private final static Singleton singleton = null; //定义一个私有的静态的Singleton类 private Singleton() { //私有的无参构造方法,出自己之外无法初始化 } //加上同步,防止多线程冲突 public synchronized static Singleton getSingleton() { //共用静态方法 获得单例对象 if(singleton == null) { singleton = new Singleton(); } return singleton; } }
把初始化Singleton的步骤交给Singleton类自己。
每种方式的优缺点可以参考《Spring的单例模式底层实现笔记》
(18).<桥接模式Bridge>(书本例子:手机软件统一)
定义:将抽象部分与它的实现部分分离,使它们都可以独立变化。
在此引入合成/聚合复用原则:尽量使用合成/聚合,尽量不要使用类继承。(什么时候使用继承,什么时候使用合成/聚合需要好好思考)
例如:大雁与翅膀属于合成,大雁与雁群属于聚合。
定义:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
1.Receiver接收者 : 命令的具体执行者。
2.Command命令类:包含Receiver对象,调用Receiver的具体执行方法。
3.Invoker调用类:包含Command对象集合,调用Command的执行方法。
作用: 1.他能较容易地设计一个命令队列
2.在需要的情况下,可以较容易的将命令记入日志
3.允许接受请求的一方决定是否要否决请求。
4.可以容易地实现对请求的撤销和重做
5.由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要急着去实现它,事实上,
在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义
(20).<职责链模式Chain of Responsibility>(书本例子:加薪请求审批链)
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
1.Handler职责接口:设置下一个职责继承者抽象方法,处理请求的抽象方法,
2.ConcreteHandler具体职责类:在处理请求的方法中,根据自己的职责等级处理请求,不然传递给下一位职责人。
当客户提交一个请求时,请求是沿链传递直至有一个ConcreteHandler对象负责处理。
接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是责任链可简化对象的相互请求连接,它们仅仅需保持一个指向其后继承者的引用,而不需保持它所有的候选接受者的引用。
随时随地增加或者修改处理一个请求的结构。增强了给对象指派职责的灵活性。
一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得不到处理(需要总和考虑)。
(21).<中介者模式Mediator>(书本例子:联合国就是个大中介)
定义:用一个中介对象来封装一些列的对象交互。中介者使各对象不需要显示相互的引用,从而使其耦合松散,而且可以独立地改变他们直接的交互。
1.Mediator中介接口:负责各个国家之间通信
2.ConcreteMediator具体中介实现:包含所有具体ConcreteCountry对象,发送命令方法
3.Country国家对象:每个国家都认识联合国,与其他国家对话时通过联合国来连接。
由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到他们之间的交互上来,也就是在一个更宏观的角度去看待系统。
中介者模式一般应用于一组对象以定义良好但是复杂的方法进行通信的场合。
(22).<享元模式Flyweight>(书本例子:项目多也别傻做)
定义:运用共享技术有效地支持大量细粒度的对象。
(单例模式就很类似了,把可能在多处共用的方法或者类,放在单例工厂中,供其他对象使用)
public abstract class Action { public abstract void getManConclusion(Man elementMan); public abstract void getWomanConclusion(Woman elementWoman); } public class Success extends Action { @Override public void getManConclusion(Man elementMan) { System.out.println("成功的男人背后都有一个伟大的女人!"); } @Override public void getWomanConclusion(Woman elementWoman) { System.out.println("成功的女人背后都有一个不成器的男人!"); } }
public abstract class Person { public abstract void Accept(Action visitor); } public class Man extends Person { @Override public void Accept(Action visitor) { visitor.getManConclusion(this); } } public class Woman extends Person { @Override public void Accept(Action visitor) { visitor.getWomanConclusion(this); } }
一般这时候会有一个对象结构体类,来管理访问者和元素。
public class ObjectStructure { private List<Person> elements = new ArrayList<Person>(); public void add(Person element) { elements.add(element); } public void drop(Person element) { elements.remove(element); } public void display(Action visitro) { for(Person element : elements) { element.Accept(visitro); } } }
书中介绍到的各项设计原则
设计原则:
(1)单一职责原则(SRP):就一个类而言,应该仅有一个引起它变化的原则。(书本例子:手机拍照功能不如摄像机)
接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
(2)开放-封闭原则(OCP):就说软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。(书本例子:考研和求职同时进行)
对于扩展是开放的,对于更改是封闭的
怎样的设计才能面对需求的改变却可以保持相对稳定,从而使得系统可以在第一个版本以后不断推出新的版本呢?
(4)迪米特原则(LOD):(书本例子:没熟人难办事?找管理就成)
如果两个类不必彼此直接通信,那么这两个类就不用当发生直接的相互作用。如果其中一个类需要另一个类的一个方法的话,可以通过第三者转发这个调用。
根本思想,是强调了类之间的松耦合。
类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不是对有关系的类造成波及。
写学习笔记比看这本书花的时间还多,但这种复习的确很有作用。