本篇是为了以后便于自己复习。
设计原则:
1.找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
简单来说,如果每次新的需求一来,就会使某方面的代码发生变化,那么这部分的代码就要被抽出来。"把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。"
2.针对接口编程,而不是针对实现编程。
3.多用组合,少用继承。
使用组合,更具弹性,在运行时动态的改变行为。
4.为了交互对象之间的松耦合设计而努力。
5.开发-关闭原则:类应该对扩展开放,对修改关闭。
6.最少知识原则:只和你的密友谈(减少对象之间的交互)
意思就是说希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,就会影响到其他部分。
7.依赖倒置原则:要依赖抽象,不要依赖具体类。(不能让高层组件依赖底层组件)
8.好莱坞原则:别调用我们(高层组件),我们来调用你。
问:好莱坞原则和依赖倒置原则有什么关系?
答:依赖倒置原则教我们尽量避免使用具体类,而多使用抽象。而好莱坞原则是在创建框架或组件上的一种技巧,让底层能被挂钩进计算中,而且又不会让高层组件依赖底层组件。
两者的目标都是在解耦,但是依赖倒置原则更加注意如何在设计中避免依赖;好莱坞原则教我们创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖它们。
9.一个类应该只有一个引起变化的原因。
内聚:当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它是低内聚。
这个原则告诉我们一个责任只委派给一个类。听起来容易,但做起来并不简单。区分设计中的责任,是最困难的事情之一。我们的大脑很习惯看着一大群的行为,然后将他们集中在一起,尽管他们可能属于两个或多个不同的责任。
成功的唯一方法就是不断的检查你的设计,随着系统的成长,随时观察有没有迹象显示某个类改变的原因超过了一个。
设计模式介绍:
1.策略模式
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。例如:鸭子游戏,针对接口编程。
2.观察者模式
定义了对象之间的一对多依赖,当一个对象改变时,它的所有依赖着都会收到通知并自动更新。例如:报社和订阅者,当报社有新消息时,会通知订阅的对象有新消息了,订阅对象也可以取消订阅。
3.装饰者模式
动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的代替方案。例如:我们点咖啡通常是配料加咖啡类型,我们要根据所加的不同配料来更改价格。于是就有了抽象组件(Beverage饮料)、具体组件(具体类型的咖啡如浓缩咖啡)、抽象装饰者(Condiment配料)、具体装饰者(具体类型的配料如奶泡、焦糖)。具体装饰者中包含了抽象对象(Beverage饮料)。
4.工厂模式
定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到了子类。
5.单例模式
确保一个类只有一个实例,并提供一个全局访问点。把某个类设置成自己管理的一个单独实例,同时避免其他类再自行产生实例。
6.命令模式
将"请求"封装为对象,以便使用不同的请求、队列或者日志参数化其他对象,命令模式也支持可撤销的操作。例如给遥控器的两个个插槽编写命令,有公共接口Command,有execute方法;然后有Open类和Close类实现这个Command并编写具体操作;然后有Control类来设置和运行命令:它包含Command接口属性引用为slot,有setCommand方法,有buttonWasPressed方法里面调用了slot.execute()。
7.适配器模式
将一个类的接口,转换成客户期望的另一种接口。适配器让原本接口不兼容的类可以合作无间。
对象适配器:例如你有Duck接口,它有quack()方法和fly()方法,现在客户想要Turkey适配Duck的方法,可以建一个TurkeyAdapter类实现Duck接口,然后它有Turkey属性,实现quack()和fly()方法里面都调用Turkey的方法。
类适配器:(java语言不支持多重继承)
8.外观模式
提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。最好遵循最少知识原则:client只有一个交互类,这个交互类封装类所有的组件,子系统也最好遵循最少知识原则。
9.模板方法模式
在一个方法中定义一个算法的骨架,而将一些具体步骤的实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
例如泡茶和冲咖啡,泡茶是烧水,浸泡茶叶,倒进杯子,加柠檬;冲咖啡是烧水,浸泡咖啡,倒进杯子,加糖。这两个非常相似,我们可以提取成公共方法:烧水和倒进杯子由抽象父类方法prepareRecipe()来做,(记得把它声明为final类型,以免子类改变这个算法的顺序)而冲泡和加调料也写父类中只不过是子类去具体实现。到时候,我们只需要新建Tea对象,直接调用Tea.prepareRecipe()。
如果子类不想做模板方法的某些动作,可以在父类模板方法下加一if语句,if(customerWantsCondiments()){addCondiments},父类返回一个true,子类可以重写customerWantsCondiments()方法返回false。
10.迭代器与组合模式
迭代器模式提供一种方法顺序访问一个聚合对象的各个元素,而又不暴露其内部的表示。
组合模式允许你将对象组合成树形结构来表现"整体/部分"层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
(组合、组件、树?组合包含组件,组件有两种:组合和叶节点。)
迭代器和组合模式配合,可以让客户不需要关心面对的是组合对象还是叶节点对象。
问题:组合内的所有对象都实现了相同的接口,也意味着某些对象具备一些没有意义的方法。
解决一:让这样的方法返回null
可以让createIterator()返回null,如果这么做,客户代码就需要条件语句判断返回值。
解决二:返回false
返回一个迭代器,而这个迭代器的hasNext()永远返回false。(这个更好用)。
所以让我们在叶子对象中的createIterator(){ return new NullIterator() },而我们的NullIterator对象有public Object next(){ return null },public boolean hasNext(){ return false },public void remove(){ throw new UnsupportedOperationException();}
11.状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
类图基本一样,但是与策略模式的区别:
状态模式:将一曲行为封装在状态对象中,context的行为随时委托在那些状态对象中的一个。context内部状态经常改变但是客户对于状态对象了解不多。
策略模式:客户主动指定context所要组合的策略对象是哪一个。策略模式能够让我们在运行时改变策略,但对于某个context对象而言,通常都只有一个适当都策略对象。
11.代理模式
为另一个对象提供替身或占位符以控制对这个对象的访问。(也可以将代理描述成另一对象的代表。)
远程代理模式:
有客户端、客服端辅助对象(stub)、服务端辅助对象(skeleton)、服务端组成。
糖果机简要流程:
a.CEO执行监视器,先取得远程糖果机端代理,然后调用每个代理的getState()。
b.代理上的getState()被调用,此调用被转发到远程服务。Skeleton接受到请求,然后转发给糖果机。
c.糖果机将状态返回给skeleton,skeleton将状态序列化,通过网络传回给代理,代理将其反序列化,把他当作一个对象返回给监视器。
虚拟代理模式:
场景:例如你的应用想要加载网络上的图片,但是这个过程是有延迟的,在加载完之前显示"正在加载中,请耐心等待",一旦加载完成,则显示该图片。
保护代理模式(java中动态代理):
场景:有一个人物关系示例,PersonBean有姓名、爱好、评分属性,你可以设置自己的姓名、爱好,但是不能自己给自己打分;别人可以给你打分,但是不能修改你的姓名、爱好。这样,就需要用java创建两个代理,我们只需要编写Handler来处理转来的方法。
现实中,代理还有太多的变体,需要我们去发现。
12.复合模式
13.定义设计模式
模式是在某种情境下,针对某种问题提出的解决方案。
总结
装饰者:包装一个对象以提供新行为。
状态:封装了基于状态的行为,并使用委托在行为之间切换。
迭代器:在对象的集合之中游走,而不暴露集合的实现。
外观:简化一群类的接口。
策略:封装可以呼唤的行为,并使用委托来决定使用哪一个。
代理:包装对象,以控制对此对象的访问。
工厂方法:由子类决定创建的具体类是哪一个。
适配器:封装对象,提供不同的接口。
观察者:让对象能够在状态改变时被通知。
模板方法:由子类决定如何实现一个算法中的步骤。
组合:客户用一致的方式处理对象集合和单个对象。
单件:确保有且只有一个对象被创建。
抽象工厂:允许客户创建对象的家族,而无需指定他们的具体类。
命令:封装请求成为对象。