七种设计原则(一) 开闭原则(设计模式的基石)

面向对象和面向过程的区别:

面向过程: git

吃 (狗.屎)算法

下雨了,我打开了雨伞.编程

面向对象:设计模式

狗 吃 (屎)架构

属性:我 雨伞 雨
动做:我打开雨伞框架

面向过程强调的是一件事情“该怎么作” 强调一次成型,是连续性的。好比制造一台高达,面向过程更像是流水线的形式。经过制定一系列规则来完成高达的拼装。ide

面向对象是一件事情“该让谁作”,而后那个谁就是对象。好比制造一台高达,面对对象更像是模块化的形式,四肢和头均可以做为那个"对象",而后拼装起来。模块化

前辈们写的:函数

a) 认识问题角度:面向过程,死物受规则被动操控;面向对象,活物主动交互。学习

b) 解决问题模块:面向过程,函数;面向对象,对象。

c) 解决问题中心角度:面向过程,Hwo,“如何作?”(流程封装为函数,“如何”就是过程,谁来作仅是参数);面向对象,Who,“谁来作?”(“谁”就是对象,如何作是他本身的操做,多个对象协同完成任务)。

d) 解决问题步骤角度:面向过程,先具体逻辑细节,后抽象问题总体;面向对象,先抽象问题总体,后具体逻辑细节。

e) 数传递角度:面向过程,参数或全局变量;面向对象,方法。

f) 关系角度:面向过程,找不到对象;面向对象,可找到过程。

g) 复用层次角度:面向过程,方法层复用;面向对象,对象层复用。

h) 新概念角度:面向过程,句柄;面向对象,构造&析构。
连接:https://www.zhihu.com/question/19701980/answer/22817355
来源:知乎

内涵和外延

说到面向对象,必定要理解两个逻辑学中的概念:内涵和外延   (比较抽象)

内涵:概念中所反映的事物的特有属性
外延:具备概念所反映的特有属性的全部事物

打个比方,你面前有一些梨子、苹果、香蕉、菠萝,它们虽然样子不一样,但它们都富含"水分",以及它们都是植物的"果实",咱们抓住它们这两个特征,就把它们简化叫"水果"。当咱们面对这堆东西,脑海中对这两个特征有一种模糊的感受,为了把这种感受说出来,咱们用了一个汉语词汇"水果",不过咱们也能够用英语词汇"fruit",用什么样的词汇不重要,重要的是这个词汇表明了一种咱们对这两种特征的总结。
除了水果之外,咱们对不少东西都有这样的总结,这些总结彼此不一样,但它们又有两个共同特征:"特色的总结"、"能和其余总结区别"。咱们就把对这些总结的总结取个了名字,叫"概念"。
再拿"水果"这个概念来讲,"富含水分"和"植物果实"两个特征,是被"水果"一词包含在内了的,咱们叫它"内涵",当把这个内涵发散出去,对应到具体的例子:苹果梨子香蕉菠萝,这里的具体例子,就是"水果"这个概念的"外延"。

面向对象的角度来讲,内涵就是类的定义,外延就是类的实例

连接:https://www.zhihu.com/question/22267682/answer/134411093
来源:知乎


理解了上面的概念咱们再来学习设计模式效果会更佳~

开闭原则(设计模式的基石):

开闭原则是面向对象中可复用设计的基石,是面向对象中最重要的原则之一。

1.定义

开闭原则强调一个软件实体(如:类,模块和函数)应该对外扩展开放,对修改关闭

对外扩展开放:模块对外扩展开放,意味着需求变化时,能够对模块扩展,使其具备改变的新行为。

模块经过扩展应对需求的变化。

对修改关闭:模块对修改关闭,表示当需求变化时,关闭对模块源码的修改,固然这里的“关闭”是尽量不修改的意思,尽可能在不修改源代码的基础上面扩展组件。

 

2.问题和解决方案

一个软件产品在声明周期内都会发生变化的,既然变化是一个事实,咱们就应该在设计时尽可能适应变化。以提升项目的稳定性和灵活性。开闭原则告诉咱们应该尽可能经过扩展软件实体的行为来完成新的变化,而不是经过修改现有代码来完成。

好比支付接口,设计时 支付流程 只有一套,而后在这一套流程的基础上扩展出来,当和某一家银行合做,就新添加一个实现,当和某一家银行终止合做时,就停用该实现。不用更改支付流程这个模块。这样在咱们有新的需求变动时,是不须要修改支付模块的(修改源代码的风险很大,你可能看不懂别人的代码),只须要在源代码的基础上进行扩展(由于源代码已经通过成千上万次的访问测试,是正确的)。这也是咱们常说的面向接口编程。先定义一个抽象类,而后抽象去界定扩展。

再好比 张全蛋开了一家书店 类图以下:

这里写图片描述

BookStore 指的是张全蛋的书店

IBook 定义了书籍的三个属性:名称、价格和做者

NovelBook 是一个具体的实现类,全部小说书籍的总称。
 

IBook接口

public interface IBook {

//书籍有名称 public String getName();

//书籍有售价 public int getPrice();

//书籍有做者 public String getAuthor();

}

public class NovelBook implements IBook { 、

//书籍名称 private String name;

//书籍的价格 private int price;

//书籍的做者 private String author;

//经过构造函数传递书籍数据

public NovelBook(String _name,int _price,String _author){

this.name = _name;

this.price = _price;

this.author = _author;

}

//得到做者是谁

public String getAuthor() {

return this.author;

}

//书籍叫什么名字

public String getName() {

return this.name;

}

//得到书籍的价格

public int getPrice() {

return this.price;

}

}

张全蛋书店是怎么销售书籍的:

public class BookStore {

private final static ArrayList<IBook> bookList = new ArrayList<IBook>();

   //静态模块初始化,项目中通常是从持久层初始化产生

   static{

    bookList.add(new NovelBook("天龙八部",3200,"金庸"));

    bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));

    bookList.add(new NovelBook("悲惨世界",3500,"雨果"));

    bookList.add(new NovelBook("金瓶梅",4300,"兰陵笑笑生"));

    }

     //模拟书店买书

     public static void main(String[] args) {

        NumberFormat formatter = NumberFormat.getCurrencyInstance();

        formatter.setMaximumFractionDigits(2);

        System.out.println("------------书店买出去的书籍记录以下:---------------------");

        for(IBook book:bookList){

        System.out.println("书籍名称:" + book.getName()+"\t书籍做者:" + book.getAuthor()+ "\t书籍价格:" + formatter.format(book.getPrice()/100.0)+"元");

        }

    }

}

------------书店买出去的书籍记录以下:---------------------

书籍名称:天龙八部 书籍做者:金庸 书籍价格:¥32.00元

书籍名称:巴黎圣母院 书籍做者:雨果 书籍价格:¥56.00元

书籍名称:悲惨世界 书籍做者:雨果 书籍价格:¥35.00元

书籍名称:金瓶梅 书籍做者:兰陵笑笑生 书籍价格:¥43.00元

到这里 张全蛋的书店上线了,全蛋很开心,项目完成了。

过了一段时间,全蛋的书店不多有人光顾。全蛋打算打折出售这批书。对于已经运行的项目来讲这就是一个新的需求。

咱们有三种方法来完成全蛋的要求:

修改接口:

在IBook上新增一个方法getOffPrice(); 在 IBook 上新增长一个方法 getOffPrice(), 专门进行打折处理, 全部的实现类实现该方法。可是这样修改的后果就是实现类 NovelBook 要修改,BookStore 中的main方法也修改, 同时 IBook做为接口应该是稳定且可靠的,不该该常常发生变化,不然接口作为契约的做用就失去了效能,——所以,该方案否认。 

修改实现类:

修改 NovelBook 类中的方法,直接在 getPrice()中实现打折处理,好办法,我相信你们在项目中常用的就是这样办法,经过 class 文件替换的方式能够完成部分业务(或是缺陷修复)变化,该方法在项目有明确的章程(团队内约束)或优良的架构设计时,是一个很是优秀的方法,可是该方法仍是有缺陷的,例如采购书籍人员也是要看价格的,因为该方法已经实现了打折处理价格,所以采购人员看到的也是打折后的价格,这就产生了信息的蒙蔽效果,致使信息不对称而出现决策失误的状况。——所以,该方案也不是一个最优的方案。
 

经过扩展实现变化:

增长一个子类 OffNovelBook,覆写 getPrice 方法,高层次的模块(也就是 static静态模块区)经过 OffNovelBook 类产生新的对象,完成对业务变化开发任务。——好办法,修改也少,风险也小,咱们来看类图: 

这里写图片描述
OffNovelBook 类继承了NovelBook,并覆写了 getPrice 方法,不修改原有的代码。咱们来看新增长的子类 OffNovelBook:

public class OffNovelBook extends NovelBook {

public OffNovelBook(String _name,int _price,String _author){

super(_name,_price,_author);

}

//覆写销售价格

    @Override

    public int getPrice(){

    //原价

    int selfPrice = super.getPrice();

    // 打8折

    int offPrice=0;

    offPrice = selfPrice * 80 /100;

    return offPrice;

    }

}

很简单,仅仅覆写了 getPrice 方法.

public class BookStore {

private final static ArrayList<IBook> bookList = new ArrayList<IBook>();

//静态模块初始化,项目中通常是从持久层初始化产生

static{

bookList.add(new OffNovelBook("天龙八部",3200,"金庸"));

bookList.add(new OffNovelBook("巴黎圣母院",5600,"雨果"));

bookList.add(new OffNovelBook("悲惨世界",3500,"雨果"));

bookList.add(new OffNovelBook("金瓶梅",4300,"兰陵笑笑生"));

}

//模拟书店买书

public static void main(String[] args) {

NumberFormat formatter = NumberFormat.getCurrencyInstance();

formatter.setMaximumFractionDigits(2);

System.out.println("------------书店买出去的书籍记录以下:---------------------");

for(IBook book:bookList){

System.out.println("书籍名称:" + book.getName()+"\t书籍做者:" + book.getAuthor()+ "\t书籍价格:" + formatter.format(book.getPrice()/100.0)+"元");

}

}

}

咱们只修改了静态模块初始化部分,其余的部分没有任何改动,看运行结果:

------------书店买出去的书籍记录以下:---------------------

书籍名称:天龙八部 书籍做者:金庸 书籍价格:¥25.60元

书籍名称:巴黎圣母院 书籍做者:雨果 书籍价格:¥50.40元

书籍名称:悲惨世界 书籍做者:雨果 书籍价格:¥28.00元

书籍名称:金瓶梅 书籍做者:兰陵笑笑生 书籍价格:¥38.70元

经过扩展完成了打折的业务,知足了全蛋的要求。

 

3.概括变化

逻辑变化:

只变化一个逻辑,而不涉及到其余模块,好比原有的一个算法是a*b+c,如今要求a*b*c,可能经过修改原有类中的方法方式来完成,前提条件是全部依赖或关联类都按此相同逻辑处理。

子模块变化:

一个模块变化,会对其余模块产生影响,特别是一个低层次的模块变化必然引发高层模块的变化,所以在经过扩展完成变化时,高层次的模块修改是必然的。

可见视图变化:

可见视图是提供给客户使用的界面,该部分的变化通常会引发连锁反应,若是仅仅是界面上按钮、文字的从新排布却是简单,最司空见惯的是业务耦合变化,什么意思呢?一个展现数据的列表,按照原有的需求是六列,忽然有一天要增长一列,并且这一列要跨度N张表,处理M个逻辑才能展示出来,这样的变化是比较恐怖的,可是咱们仍是能够经过扩展来完成变化,这就依赖咱们原有的设计是否灵活。

 

4.优势:开闭原则提升了系统的可维护性和代码的重用性

可复用性好。

咱们能够在软件完成之后,仍然能够对软件进行扩展,加入新的功能,很是灵活。所以,这个软件系统就能够经过不断地增长新的组件,来知足不断变化的需求。

可维护性好。

因为对于已有的软件系统的组件,特别是它的抽象底层不去修改,所以,咱们不用担忧软件系统中原有组件的稳定性,这就使变化中的软件系统有必定的稳定性和延续性。

 

5.如何使用开闭原则:

写代码以前必定要多想,多考虑。要对可能扩展的需求有前瞻性和预见性(此处须要经验...)

实现开闭原则的关键就在于“抽象”。把系统的全部可能的行为抽象成一个抽象底层,这个抽象底层规定出全部的具体实现必须提供的方法的特征。做为系统设计的抽象层,要预见全部可能的扩展,从而使得在任何扩展状况下,系统的抽象底层不需修改;同时,因为能够从抽象底层导出一个或多个新的具体实现,能够改变系统的行为,所以系统设计对扩展是开放的。

咱们在软件开发的过程当中,一直都是提倡需求导向的。这就要求咱们在设计的时候,要很是清楚地了解用户需求,判断需求中包含的可能的变化,从而明确在什么状况下使用开闭原则。

关于系统可变的部分,还有一个更具体的对可变性封装原则(Principle of Encapsulation of Variation, EVP),它从软件工程实现的角度对开闭原则进行了进一步的解释。EVP要求在作系统设计的时候,对系统全部可能发生变化的部分进行评估和分类,每个可变的因素都单独进行封装。

咱们在实际开发过程的设计开始阶段,就要罗列出来系统全部可能的行为,并把这些行为加入到抽象底层,根本就是不可能的,这么去作也是不经济的。所以咱们应该现实的接受修改拥抱变化,使咱们的代码能够对扩展开放,对修改关闭。

尽可能不修改原来的代码

除非是修改原来代码中的错误,不然尽可能不要去修改原来的代码,可是也有例外,好比扩展了底层模块,高层模块仍是须要发生一些变化的,否则低层模块的扩展就是没有任何意义的代码片断;

以抽象代替实现

这也是咱们一直所说的面向接口编程,固然这里的抽象并不只仅是指接口,还能够是抽象类;

以抽象隔离变化

首先,不管模块是多么的“封闭”,都会存在一些没法对之封闭的变化,既然不可能彻底封闭,那么设计人员必须对于其余设计的模块应该对哪一种变化封闭进行选择,他必须猜想出最有可能发生的变化种类,而后构造抽象来隔离变化,其次,咱们并无未卜先知的能力,因此在最初编写代码时能够假设变化不会发生,可是当变化发生时,咱们就须要去建立抽象来隔离之后发生的同类的变化;

抽象层设计到整个项目的架构,所以抽象层须要尽可能保持稳定,一旦肯定就不要轻易修改;

避免不合理的抽象

开闭原则须要使用抽象,可是过分的抽象或者说不合理的抽象一样会带来很大的问题,所以抽象应该作到合理的抽象;

其余设计原则是实现开闭原则的一种手段

其中单一原则要求作到类的职责单一,里式替换原则要求不能破坏继承体系,依赖倒置原则要求咱们要面向接口编程,接口隔离原则要求作到接口要精简单一,迪米特法则则是要求作到下降耦合度,若是遵循了前面的五个法则,那么天然的也就作到了开闭原则,所以说开闭原则是设计原则的总纲

6.总结:

开闭原则:用抽象构建框架,用实现扩展细节。 

感谢百度百科~

看只有一丢丢做用,仍是要多写,多用,多总结,多思考才能体会其中的奥秘。 小伙子当你看到这里说明你已经深得朕的真传~

连接自http://blog.csdn.net/zhengzhb/article/details/7296944

看完这些前辈写的,感受收获满满~    仍是要多写,多用才能体会其中的奥秘。

相关文章
相关标签/搜索