Java常见设计模式学习(非原创)
文章大纲
一、策略模式
二、观察者模式
三、工厂模式
四、单例模式
五、其他模式
六、设计模式总结
七、参考文章
一、策略模式
现在假设我们有个“鸭子项目”,首先我们用oop(面向对象)的角度设计这个项目,找到鸭子*同的特性抽取在父类中并具体实现,不同的特性不实现,由子类具体实现,好下面看代码:
public abstract class duck { /** * 叫声和游泳为相同的特性抽取并具体实现 */ public void quack() { system.out.println("~~gaga~~"); } public void swim() { system.out.println("~~im swim~~"); } /** * 外貌为不同的特性设计为抽象的方法,有子类具体实现 */ public abstract void display(); }
现在我们看它的子类:
public class greenheadduck extends duck { @override public void display() { system.out.println("**greenhead**"); } }
public class redheadduck extends duck { @override public void display() { system.out.println("**redhead**"); } }
好的,现在我们可以看到使用oop可以很好的解决目前的问题,但是我们往往是需求不断,所以我们现在又来一个新需求:添加会飞的鸭子
好办啊,我们只要在父类中添加一个新的方法:
public abstract class duck { /** * 叫声和游泳为相同的特性抽取并具体实现 */ public void quack() { system.out.println("~~gaga~~"); } public void swim() { system.out.println("~~im swim~~"); } /**针对新需求的方法*/ public void fly() { system.out.println("~~im fly~~"); } /** * 外貌为不同的特性设计为抽象的方法,有子类具体实现 */ public abstract void display(); }
问题来了,这个fly让所有子类都会飞了,这是不科学的。
继承的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。影响会有溢出效应
好现在我们继续用oop的方式去解决,使其子类覆盖fly:
public class greenheadduck extends duck { @override public void display() { system.out.println("**greenhead**"); } /** * 覆盖 * */ public void fly() { system.out.println("~~no fly~~"); } }
可以看到貌似问题是解决了,我们现在的鸭子类只有2种,如果有上百种呢,那不是都要去覆盖。所以这不是一种好的设计模式
分析问题:
需要新的设计方式,应对项目的扩展性,降低复杂度:
1)分析项目变化与不变部分,提取变化部分,抽象成接口+实现;
2)鸭子哪些功能是会根据新需求变化的?叫声、飞行…
我们将变化的功能设计成接口,下面看代码:
public interface flybehavior { void fly(); }
public interface quackbehavior { void quack(); }
来看看新的duck类:
public abstract class duck { /** * 父类定义行为出来,但是没有具体实例化 */ flybehavior mflybehavior; quackbehavior mquackbehavior; public duck() { } public void fly() { if (mflybehavior!=null) { mflybehavior.fly(); } } public void quack() { if (mquackbehavior!=null) { mquackbehavior.quack(); } } /** * 子类可以透过两个行为的set方法去动态改变自己的具体行为 */ public void setmflybehavior(flybehavior mflybehavior) { this.mflybehavior = mflybehavior; } public void setmquackbehavior(quackbehavior mquackbehavior) { this.mquackbehavior = mquackbehavior; } public abstract void display(); }
然后在去看看子类:
public class redheadduck extends duck{ public redheadduck() { mflybehavior=new goodflybehavior(); mquackbehavior=new gagaquackbehavior(); } @override public void display() { system.out.println("redduck"); } }
public class greenheadduck extends duck{ public greenheadduck() { mflybehavior=new badflybehavior(); mquackbehavior=new gegequackbehavior(); } @override public void display() { system.out.println("greenduck"); } }
再来看看接口实现类:
public class badflybehavior implements flybehavior{ @override public void fly() { system.out.println("bad fly"); } }
public class goodflybehavior implements flybehavior{ @override public void fly() { system.out.println("good fly"); } }
public class noflybehavior implements flybehavior{ @override public void fly() { system.out.println("no fly"); } }
public class gagaquackbehavior implements quackbehavior{ @override public void quack() { system.out.println("gaga quack"); } }
public class gegequackbehavior implements quackbehavior{ @override public void quack() { system.out.println("gege quack"); } }
public class noquackbehavior implements quackbehavior{ @override public void quack() { system.out.println("no quack"); } }
好的,现在我们来分析一下这个设计,在父类中我们定义好flybehavior & quackbehavior 两个行为接口,然后在子类构造方法中分别设定对应的具体行为
现在来测试一下:
策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为算法的变化独立于算法的使用者
二、观察者模式
现在假设我们有一个需要为a公司实时提供天气的天气预报接口项目,好的,首先我们还是以oop的方式去解决问题,首先我们创建一个天气台对象并提供相关方法假设它可以实时为a公司提供天气数据,下面看代码:
public class meteorologicalstation { private float pressure; private float temperature; private float humidity; private acompany company; public meteorologicalstation(acompany company) { this.company=company; } public float getpressure() { return pressure; } public float gettemperature() { return temperature; } public float gethumidity() { return humidity; } /** * 实时提供天气情况 * */ public void uploaddata(float pressure,float temperature,float humidity){ company.getmeteorologicalstationdata(pressure, temperature, humidity); } }
acompany为a公司:
public class acompany { public void getmeteorologicalstationdata(float pressure, float temperature, float humidity) { system.out.println("pressure: "+pressure+",temperature: "+temperature+",humidity: "+humidity); } }
测试
好的,到了这里我们从oop角度已经初步解决了这个项目,假设现在b公司也需要实时获取天气台提供的天气情况,如果我们还是继续使用oop去设计这个项目,需要在天气台接口中添加bcompany,并在实时更新的函数中调用bcompany的获取天气的函数,这貌似可以解决这个需求,但是加入后续加入c,d,e..公司,那么天气台接口修改的代码也是比较大,但是我们知道在通常实际开发中,接口一般不会随着他人的接入而更改,所以现在我们使用观察者模式去设计这个项目:
/** * 该接口相当于天气台管理者,谁想接入我和离开我都必须通过它去管理 * */ public interface subject { void registerobserver(observer o); void removeobserver(observer o); void notifobserver(); }
/** * 通过该接口,每个想要接入的公司实现该方法即可。 */ public interface observer { void getmeteorologicalstationdata(float pressure,float temperature,float humidity); }
public class meteorologicalstation implements subject { private float pressure; private float temperature; private float humidity; private arraylist<observer> observers; public meteorologicalstation(acompany company) { observers = new arraylist<observer>(); } public float getpressure() { return pressure; } public float gettemperature() { return temperature; } public float gethumidity() { return humidity; } public void uploaddata(float pressure, float temperature, float humidity) { this.pressure = pressure; this.temperature = temperature; this.humidity = humidity; notifobserver(); } @override public void registerobserver(observer o) { if (!observers.contains(o)) { observers.add(o); } } @override public void removeobserver(observer o) { if (observers.contains(o)) { observers.remove(o); } } @override public void notifobserver() { for (int i = 0; i < observers.size(); i++) { observer observer = observers.get(i); observer.getmeteorologicalstationdata(getpressure(), gettemperature(), gethumidity()); } } }
现在我们来分析天气台类,天气台实现了subject,并用集合的形式方便的管理多个客户的接入与离开,在notifobserver中实时的上传天气数据。好的,现在我们看看其他客户的接入:
public class acompany implements observer{ @override public void getmeteorologicalstationdata(float pressure,float temperature,float humidity){ system.out.println("a pressure: "+pressure+",temperature: "+temperature+",humidity: "+humidity); } }
public class bcompany implements observer{ @override public void getmeteorologicalstationdata(float pressure, float temperature, float humidity) { system.out.println("b pressure: "+pressure+",temperature: "+temperature+",humidity: "+humidity); } }
好的我们可以看到无论有多少个公司想要接入,现在只需要实现observer接口即可。现在我们来测试下:
看到这里我们已经知道了观察者模式的好处,下面我们看看java内置的观察者:
public class meteorologicalstation extends observable { private float pressure; private float temperature; private float humidity; public float getpressure() { return pressure; } public float gettemperature() { return temperature; } public float gethumidity() { return humidity; } public void uploaddata(float pressure, float temperature, float humidity) { this.pressure = pressure; this.temperature = temperature; this.humidity = humidity; weatherdata data=new weatherdata(pressure, temperature, humidity); /** * setchanged * 根据有些特定的需求出现的,设置后唤醒才有效, * 例如我们不需要温度改变0.5我们也去唤醒客户, * 因此这里我们可以判断后在设置 * */ this.setchanged(); this.notifyobservers(data); } public class weatherdata{ public float pressure; public float temperature; public float humidity; public weatherdata(float pressure, float temperature, float humidity) { this.pressure = pressure; this.temperature = temperature; this.humidity = humidity; } } }
从代码中可以看到observable 是一个类 而不是像我们一样的subject的接口
this.setchanged(); this.notifyobservers(data);
两个一起使用才有效,下面我们看看a,b公司需要怎么做:
public class acompany implements observer{ @override public void update(observable arg0, object arg1) { weatherdata data=(weatherdata) arg1; system.out.println("a pressure: "+data.pressure+",temperature: "+data.temperature+",humidity: "+data.humidity); } }
public class bcompany implements observer{ @override public void update(observable o, object arg) { weatherdata data=(weatherdata) arg; system.out.println("b pressure: "+data.pressure+",temperature: "+data.temperature+",humidity: "+data.humidity); } }
a,b公司只要实现系统的observer接口,和我们刚才的设计是一样的。最后我们在测试一下:
可以看到和我们之前的效果完全一样
三、工厂模式
项目需求:生产披萨项目,生产披萨需要经过准备,烘烤,剪切,打包 4个步骤,根据客户输入的披萨名字生产对应的披萨。
首先用传统的方式去做:
public abstract class pizza { protected string name; public abstract void prepare(); public void bake() { system.out.println(name+" baking;"); } public void cut() { system.out.println(name+" cutting;"); } public void box() { system.out.println(name+" boxing;"); } public void setname(string name) { this.name=name; } }
public class pepperpizza extends pizza{ @override public void prepare() { super.setname("pepperpizza"); system.out.println("pepperpizza"); } }
public class greekpizza extends pizza{ @override public void prepare() { super.setname("greekpizza"); system.out.println("greekpizza"); } }
public class cheesepizza extends pizza{ @override public void prepare() { super.setname("cheesepizza"); system.out.println("cheesepizza"); } }
public class orderpizza { orderpizza(){ string type=null; pizza pizza=null; do { type=gettype(); if (type.equals("greek")) { pizza=new greekpizza(); }else if (type.equals("cheese")) { pizza=new cheesepizza(); }else if (type.equals("pepper")) { pizza=new pepperpizza(); }else{ break; } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } while (true); } private string gettype(){ string type=null; bufferedreader br=null; try { br=new bufferedreader(new inputstreamreader(system.in)); system.out.println("input pizza type:"); type= br.readline(); } catch (ioexception e) { e.printstacktrace(); return ""; } return type; } }
代码分析:首先我们考虑到生产披萨的4步骤是一样的,只有名字不一样,所以我们将名字方法抽象出来,友各个披萨类去实现,然后通过orderpizza 类去模仿工厂生产的过程。
最后我们来测试一下:
public class test { public static void main(string[] args) { orderpizza order1=new orderpizza(); } }
然后现在又研究出了几个新的披萨品种,需要加入到系统中,那么如果继续在传统的方式中修改,我们只需要增加新品种披萨类,然后修改 orderpizza 中的判断语句。
我们现在用简单工厂模式下试一下:
简单工厂类中各种披萨类还是一样,修改的是orderpizza:
public class orderpizza { orderpizza(simplefactorypizza factory){ string type=null; pizza pizza=null; do { type=gettype(); pizza=factory.createpizza(type); if (pizza!=null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else{ break; } } while (true); } private string gettype(){ string type=null; bufferedreader br=null; try { br=new bufferedreader(new inputstreamreader(system.in)); system.out.println("input pizza type:"); type= br.readline(); } catch (ioexception e) { e.printstacktrace(); return ""; } return type; } }
public class simplefactorypizza { public pizza createpizza(string type){ pizza pizza=null; if (type.equals("greek")) { pizza=new greekpizza(); }else if (type.equals("cheese")) { pizza=new cheesepizza(); }else if (type.equals("pepper")) { pizza=new pepperpizza(); }else if (type.equals("beef")) { pizza=new beefpizza(); } return pizza; } }
虽然看似代码是一样的,但是我们已经将变化的代码抽取出来了,在orderpizza中我们无需再次修改,此时我们已经将变化的和不变化的隔离开来了
然后现在我们需要在纽约,伦敦生产披萨,且各地生成的披萨都各有差异,那么我们现在使用工厂方法模式试试:
public class ldcheesepizza extends pizza{ @override public void prepare() { super.setname("ldcheesepizza"); system.out.println("ldcheesepizza"); } }
public class ldgreekpizza extends pizza{ @override public void prepare() { super.setname("ldgreekpizza"); system.out.println("ldgreekpizza"); } }
public class ldpepperpizza extends pizza{ @override public void prepare() { super.setname("ldpepperpizza"); system.out.println("ldpepperpizza"); } }
纽约的也一样,我们再来看看orderpizza
public abstract class orderpizza { public abstract pizza createpizza(string type); public orderpizza(){ string type=null; pizza pizza=null; do { type=gettype(); pizza=createpizza(type); if (pizza!=null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else{ break; } } while (true); } private string gettype(){ string type=null; bufferedreader br=null; try { br=new bufferedreader(new inputstreamreader(system.in)); system.out.println("input pizza type:"); type= br.readline(); } catch (ioexception e) { e.printstacktrace(); return ""; } return type; } }
这里因为工厂是多样化的,所以这里我们将工厂抽象,然后看具体实现:
public class newyorkorderpizza extends orderpizza { @override public pizza createpizza(string type) { pizza pizza=null; if (type.equals("greek")) { pizza=new ldgreekpizza(); }else if (type.equals("cheese")) { pizza=new ldcheesepizza(); }else if (type.equals("pepper")) { pizza=new ldpepperpizza(); } return pizza; } }
public class londonorderpizza extends orderpizza { @override public pizza createpizza(string type) { pizza pizza=null; if (type.equals("greek")) { pizza=new nygreekpizza(); }else if (type.equals("cheese")) { pizza=new nycheesepizza(); }else if (type.equals("pepper")) { pizza=new nypepperpizza(); } return pizza; } }
最后我们来测试一下
public class test { public static void main(string[] args) { orderpizza order1=new newyorkorderpizza(); } }
最后我们再试试使用抽象工厂模式:
public interface factorypizza { pizza createpizza(string type); }
单独将变化的工厂抽象
public class orderpizza { orderpizza(factorypizza factory){ string type=null; pizza pizza=null; do { type=gettype(); pizza=factory.createpizza(type); if (pizza!=null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }else{ break; } } while (true); } private string gettype(){ string type=null; bufferedreader br=null; try { br=new bufferedreader(new inputstreamreader(system.in)); system.out.println("input pizza type:"); type= br.readline(); } catch (ioexception e) { e.printstacktrace(); return ""; } return type; } }
public class londonfactorypizza implements factorypizza{ @override public pizza createpizza(string type) { pizza pizza=null; if (type.equals("greek")) { pizza=new ldgreekpizza(); }else if (type.equals("cheese")) { pizza=new ldcheesepizza(); }else if (type.equals("pepper")) { pizza=new ldpepperpizza(); } return pizza; } }
public class newyorkorderpizza implements factorypizza{ @override public pizza createpizza(string type) { pizza pizza=null; if (type.equals("greek")) { pizza=new nygreekpizza(); }else if (type.equals("cheese")) { pizza=new nycheesepizza(); }else if (type.equals("pepper")) { pizza=new nypepperpizza(); } return pizza; } }
效果和工厂方法模式是一样的,虽然这里可能看不出抽象工厂模式与工厂方法模式的差别,但是我们在大的项目就可以看出来了
四、单例模式
巧克力工厂项目,工厂只有一个锅炉,往锅炉中添加佐料,如果满了则其他人不能再次添加,或者必须倾倒后添加,然后将锅炉烧开,如果锅炉已经烧开就无需再次烧开,或者必须倾倒后再次烧开
好的,首先我们还是用普通的方式去做:
public class chocolatefactor { private boolean isempty; private boolean isboiled; public chocolatefactor() { isempty=true; isboiled=false; } public void fill(){ if (isempty) { isempty=false; isboiled=false; system.out.println("锅炉满了"); } } public void boil(){ if ((!isempty)&&(!isboiled)) { isboiled=true; system.out.println("锅炉已经沸腾了"); } } public void dump(){ if ((!isempty)&&(isboiled)) { isempty=true; isboiled=false; system.out.println("锅炉啥都没有咯"); } } }
然后我们测试下:
public class test { public static void main(string[] args) { chocolatefactor factor=new chocolatefactor(); factor.fill(); factor.boil(); chocolatefactor factor1=new chocolatefactor(); factor1.fill(); factor1.boil(); } }
可以看到即使锅炉满了和沸腾了,其他人仍然可以往锅炉中添加,所以这是不合常理的,现在我们来用单例试试:
public class chocolatefactor { private boolean isempty; private boolean isboiled; private static chocolatefactor instance=null; private chocolatefactor() { isempty=true; isboiled=false; } public static chocolatefactor getinstance(){ if (instance==null) { instance=new chocolatefactor(); } return instance; } public void fill(){ if (isempty) { isempty=false; isboiled=false; system.out.println("锅炉满了"); } } public void boil(){ if ((!isempty)&&(!isboiled)) { isboiled=true; system.out.println("锅炉已经沸腾了"); } } public void dump(){ if ((!isempty)&&(isboiled)) { isempty=true; isboiled=false; system.out.println("锅炉啥都没有咯"); } } }
代码中可以看到我们将构造方法私有化,提供一个静态的方法去返回巧克力工厂实例,将巧克力工厂静态化,使其在内存中只保留一份。现在我们来测试下:
public class test { public static void main(string[] args) { chocolatefactor factor=chocolatefactor.getinstance(); factor.fill(); factor.boil(); chocolatefactor factor1=chocolatefactor.getinstance(); factor1.fill(); factor1.boil(); } }
可以看到锅炉满了和沸腾之后对其再次操作是无效的。
但是其实这也是存在问题的,现在假设我们在多线程的状态,线程一走到了if (instance==null) { 后cpu不让它工作了,开始线程二工作,线程二将整个getinstance走完了,现在cpu又让线程1执行,这时候,线程1又会去new 一个新的巧克力工厂,这样又产生了和第一个方式同样的问题,
在线程中我们学过同步锁,我们可以简单的在:
public static synchronized chocolatefactor getinstance(){ if (instance==null) { instance=new chocolatefactor(); } return instance; }
但是我们知道加入synchronized 是非常耗内存的,所以这也不是我们的最终解决办法,然后我们再次看到另一个解决办法:
public class chocolatefactor { private boolean isempty; private boolean isboiled; // private static chocolatefactor instance=null; private static chocolatefactor instance=new chocolatefactor(); private chocolatefactor() { isempty=true; isboiled=false; } // public static synchronized chocolatefactor getinstance(){ // if (instance==null) { // instance=new chocolatefactor(); // } // return instance; // } public static synchronized chocolatefactor getinstance(){ return instance; } public void fill(){ if (isempty) { isempty=false; isboiled=false; system.out.println("锅炉满了"); } } public void boil(){ if ((!isempty)&&(!isboiled)) { isboiled=true; system.out.println("锅炉已经沸腾了"); } } public void dump(){ if ((!isempty)&&(isboiled)) { isempty=true; isboiled=false; system.out.println("锅炉啥都没有咯"); } } }
可以看到和之前的代码不同的是我们直接在定义变量的时候就直接将工厂new出来,然后在getinstance中直接返回,这种方式成为懒汉式,但是问题又来了,假如我们不需要使用getinstance,然后在使用这个的类的时候我们就已经在变量中直接将工厂new了出来,所以这样也是浪费了内存,那么我们的最终解决办法是:
public static synchronized chocolatefactor getinstance(){ if (instance==null) { synchronized (chocolatefactor.class) { if (instance==null) { instance=new chocolatefactor(); } } } return instance; }
使用双重判断加锁的方式,现在我们还是在多线程的状态,加入线程1还是在if (instance==null) { cpu不分配给它空间了,线程2开始工作,线程2一鼓作气将getinstance走完,现在线程1工作了,线程走到
if (instance==null) 方法instance已经不为null了,所以他将不会再次new 一个,所以这才是我们的终极方案。
五、其他模式
参考文章:
https://blog.csdn.net/qq_33750826/article/details/73558492
1. 装饰者模式
装饰着模式是一种开放-关闭原则的设计意义,开放即为以后设计添加子类方便,关闭即父类不用更改,具体查看参考文章的第四点
2. 命令模式
具体查看参考文章的第七点
3. 适配器模式
适配器通俗易懂相当于就是一个转换器,平时手机充电,假如这个头不能插入手机,我们只需要通过一个转换器,然后使用转换器上的插头即可。具体查看参考文章的第八点
4. 外观模式
具体查看参考文章的第九点
5. 模板模式
具体查看参考文章的第十点
6. 迭代器模式
具体查看参考文章的第十一点
7. 组合模式
具体查看参考文章的第十二点
8. 状态模式
具体查看参考文章的第十三点
9. 代理模式
具体查看参考文章的第十四点
10. 复合模式
具体查看参考文章的第十五点
11. 桥接模式
具体查看参考文章的第十六点
12. 生成器模式
具体查看参考文章的第十七点
13. 责任链模式
具体查看参考文章的第十八点
14. 解释器模式
具体查看参考文章的第二十点
15. 中介者模式
具体查看参考文章的第二十一点
16. 备忘录模式
具体查看参考文章的第二十二点
17. 原型模式
具体查看参考文章的第二十三点
18. 访问者模式
具体查看参考文章的第二十四点
六、设计模式总结
1. 什么是设计模式
模式:在某些场景下,针对某类问题的某种通用解决方案
场景:项目环境
问题:约束条件,项目目标等
解决方案:通用、可以复用的设计,解决约束,达到目标
2. 设计模式的三个分类
2.1 创建型模式
对象实例化的模式,创建型模式解耦了对象的实例化过程:
简单工厂:一个工厂类根据传入的参量决定创建出哪一种产品类的实例
工厂方法:定义一个创建对象的接口,让子类决定实例化哪一个类
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类
单例模式:某个类只能有一个实例,提供一个全局访问点
生成器模式:封装一个复杂对象的构建过程,并可以按步骤构造
原型模式:通过复制现有的实例来创建新的实例
2.2 结构型模式
把类或对象结合在一起形成更大的结构:
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构
装饰模式:动态地给对象添加新的功能
代理模式:为其他对象提供一个代理以控制对这个对象的访问
蝇量模式:通过共享技术有效地支持大量细粒度的对象
外观模式:提供统一的方法来访问子系统的一群接口
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化
2.3 行为型模式
类和对象如何交互,及划分责任和算法:
模板模式:定义一个算法结构,而将一些步骤延迟到子类中实现
解释器模式:给定一个语言, 定义它的文法的一种表示,并定义一个解释器
策略模式:定义一系列的算法,把它们封装起来, 并且使它们可相互替换
状态模式:允许一个对象在其内部状态改变时改变它的行为
观测者模式:对象间的一对多的依赖关系
备忘录模式:在不破坏封装性的前提下,保存对象的内部状态
中介者模式:用一个中介对象来封装一系列的对象交互
命令模式:将命令请求封装为一个对象,使得可用不同的请求来进行参数化
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素新的功能
责任链:请求发送者和接收者之间解耦,使的多个对象都有机会处理这个请求
迭代器:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构
3. 设计模式的六大原则
3.1 组合复用原则
多用组合,少用继承
找到变化部分,抽象,封装变化
区分“has-a”与“is-a”如果hasa表示你有它应该使用组合,isa表示你是它应该使用继承。
3.2 依赖倒置原则
依赖:成员变量、方法参数、返回值
要依赖于抽象,不要依赖于具体
高层模块不应该依赖低层模块,二者都应该依赖其抽象
抽象不应该依赖具体,具体应该依赖抽象
针对接口编程,不要针对实现编程
以抽象为基础搭建的结构比具体类搭建的结构要稳定的多
在java中,抽象指的是接口或者抽象类,具体就是具体的实现类
3.3 开闭原则
对扩展开放,对修改关闭
通过扩展已有软件系统,可以提供新的功能
修改的关闭,保证稳定性和延续性
3.4 迪米特法则
一个对象应该与其他对象保持最少的了解。只与直接朋友交谈。
成员变量、方法参数、方法返回值中需要的类为直接朋友
类与类之间的关系越密切了解越多,耦合度越大
尽量降低类与类之间的耦合
外观模式、中介者模式
接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上
3.5 里氏替换原则
所有引用基类的地方必须能透明地使用其子类对象
子类在扩展父类功能时不能破坏父类原有的功能
使用继承时,遵循里氏替换原则:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
当子类重载父类方法时,方法的形参要比父类方法的参数更宽松
当子类实现父类的抽象方法时,方法的返回值要比父类更严格
里氏替换原则是设计整个继承体系的原则
3.6 单一职责原则
类应该只有一个导致类变更的理由
即一个类只负责一项职责
降低类的复杂度
提高系统的可维护性
修改时降低风险溢出
4. 模式使用思考
4.1 保持简单
尽可能用最简单的方式解决问题
简单而弹性的设计,一般使用模式是最好的方法
4.2 设计模式非万能
模式是通用问题的经验总结
使用模式时要考虑它对其他部分的影响
不需要预留任何弹性的时候,删除掉模式
平衡与妥协
4.3 何时需要模式
找出设计中会变化的部分,通常就是需要考虑模式的地方
重构时
4.4 重构的时间就是模式的时间
重构就是改变代码来改进组织方式的过程
利用模式来重构