程序设计几大原则

一、单一职责原则(SRP)

单一职责原则(SRP)用于指导我们,在对功能划分到具体的类中的时候,要保证具有高内聚性。对于SRP的一个很好的描述是:就一个类而言,应该仅有一个引起它变化的原因

想要使用好SRP,一个首先要搞清楚的问题是:什么是职责?每一个职责都是变化的一个轴线,职责被定义为"变化的原因"。

这里对于职责的定义个人感觉比较清楚明确。我们要知道,SRP中的职责并不是一个类中的函数,而是一个变化的轴线,具体到不同层次的类,其职责有大有小。如对于一个Activity来说,其负责的是展示一个完整的界面,那么界面的内容获取势必从他里面发起,界面的数据处理势必在其里面完成,一个Activity里面做的事情,调用的方法有很多,但是站在其层次的角度来考虑,一个Activity的职责就是负责好他所对应的页面,而其他页面的事情对它来说就是多余的职责。

即SRP告诉我们,当一个类中某几个功能常常衍生出新的场景、新的实现方式时,那么应该考虑将他们各自进行独立的职责封装,而不是在当前类中不断的改来改去、加来加去来使得当前类变得臃肿、难以维护。

1、一个例子

举一个例子,假设需要装一台电脑,以下是配置:

public class Computer {
    private String CPU() {
        return "最贵的最好的CPU";
    } 
    private String board(){
        return "最实惠的、型号对应的主板";   
    }
    
    private void build() {
        String cpu = CPU();
        String board = board();
        return "电脑配置为"+cpu+board;
    }
}

当前来看这个类很单纯,就是组装一台电脑嘛,完全可以胜任,目前的方案是在CPU上花钱,主板挑一个过得去的就行;但是,不同的人需求不一样,有的人追求够用即可在CPU上也追求实惠,有的人土豪一个,在主板上也追求最贵最好。这样的需求变动不得不使我们在Computer类中添加对应的方法来满足以上需求,这样就导致了Computer类的臃肿和难以维护。

上述的问题在于,在可预见的未来,用户选择CPU的方案和选择board的方案都会产生很多变化,即Computer类变化的轴线有两个:CPU和board,这违反了SRP,所以方案就是将CPU的选择方案封装成一个单独的类CPU,将board的选择方案封装成一个单独的类Board。同时,这个例子也体现了单一职责也是分层次的,Computer的职责是负责组装电脑,CPU的职责是负责确定哪款CPU,Board的职责是负责确定哪款主板。

另外,在上述问题中CPU的选择方案与主板之间还存在一种约束关系(型号对不上),因此,如果不将职责进行拆分的话,build方法还可能因为耦合关系,出现运行错误的情况,而这种情况当代码量很大时是很难察觉的。如果进行了职责的拆分,Computer类只负责CPU和board方案的匹配校验工作,那么这种错误发生的概率就会降低很多。

2. 小结

通过上述例子我们可以粗略大胆的认为当一下情况出现的时候,你的类违背了SRP,并且到了需要拆分的时候:

  1. 当前类包含了多个子功能的具体实现;
  2. 在应用场景中,上述子功能分别会有多种可能的实现方案。

二、开放封闭原则(OCP)

开闭原则(OCP):软件实体应该是可扩展的,但是不可修改的。

按照OCP设计出的模块具有两个特征:

  1. 对扩展开放:模块的行为是可以扩展的;当应用的需求改变时,可以对模块进行扩展来满足新的行为。
  2. 对更改封闭:对模块进行扩展时,不必改变模块的源码。

上面的两个特征看起来有些矛盾,不改变源码怎么进行扩展?实际上OCP要求我们使用抽象来定义行为,使用具体类来实现行为,扩展也就是说使用新的具体类来扩展行为;封闭也就是说在使用该行为时使用抽象类对象来囊括不同具体行为,这样一来就不用了更改源码了(实际上由于多了个具体类,那么最起码初始化该具体类对象的地方还是相当于修改了源码,这是没法避免的)。

同时,还有一个重点在于,在使用OCP来定义行为的时候,一定要选择程序中呈现频繁变化的那些部分进行抽象;如若不然,那么滥用OCP也会带来很高的维护成本

小结

通过上面的说明,我们对OCP可以有一个大概的认知,即具有如下结构的应用:

  1. 定义类时采用抽象类,实例化时采用具体类,即所谓的左类型与右类型。

三、里式替换原则(LSP)

上述的OCP核心思想在于:利用抽象来定义行为,利用具体类来实现和实施不同种的行为。但是,在面向对象开发中还有另一种形式的继承机制,即父类并不是抽象类,在一些情况下父类可以胜任很多工作,但有时候需要子类对象来扩展一些工作,而不得不实例化子类对象,这时候就需要要求在实例化子类对象时,他要能够完成其父类角色的任务。

即:在大多数情况下Parent a = new Parent();a.fun1();即可完成工作,但有时候需要使用到子类:Parent b = new Child();b.fun1();b.fun2();才能够完成工作。那么就需要子类的fun1()方法能够像父类的该方法一样承担应有的工作。

LSP的解释如下:子类型必须能够替换掉他们的基类型。并且在替换掉基类型之后,程序依然能够运行。

1. 一个例子

假设父类的sort函数可以实现对数组的升序排序:

class Parent {
    int[] array;
    public Parent(int[] a) {
        this.array = a;
    }
    
    public void sort() {
        实现对array的升序排序
        ...
    }
}

class Child {
    ...
    @override  
    public void sort(){
        实现对array的降序排序
    }
}

很显然,上述的子类虽然是继承了父类,并且重写了父类的sort方法,但是将全局的所有父类对象的实现变为子类对象,那么程序肯定会出问题。这就是LSP所约束的问题。

2. 小结

LSP和OCP看起来都是在说继承的问题,但是他们所关注的场景不同,而且可以看出OCP显然是遵从了LSP的,因为OCP的背景为不同子类去扩展抽象类所定义的行为的。

而LSP则告诉我们,子类在覆盖父类的方法的时候不能够任意实现,而是要遵循父类对该方法的期望与要求。为此,有人提出了一种契约设计的方式:

  1. 契约是通过为每一个方法声明的前置条件和后置条件来指定的;
  2. 要使一个方法得以执行,前置条件必须要为真;执行完毕后,后置条件必须为真;
  3. 派生类的前置条件和后置条件的规则为:在重新声明派生类中的例程时,只能使用相等或更弱的前置条件来替换基类的前置条件;只能使用相等或更强的后置条件来替换基类的后置条件。

四、依赖倒置原则(DIP)

开门见山,该原则的解释为:

  1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;
  2. 抽象不应该依赖于细节,细节应该依赖于抽象。

如果高层模块依赖于低层模块,那么在不同的上下文中重用高层模块会变得非常困难。

首先要明确一点的是,既然是高层模块,那么其势必要使用低层模块提供的功能或者说服务,如何做到高层不依赖于低层呢?答案就在后半句,抽象,不过要加上一种要求:抽象由高层模块来定义,低层模块去实现它。这样一来,高层模块就可以通过抽象类对象来使用想要的服务,而不必理会低层模块是如何实现它的,从而避免了对低层模块的依赖。这也体现了第二个要求,是细节依赖抽象而不是抽象依赖细节,即高层模块一旦定义了想要的服务,就不必理会低层模块的具体实现方式,即这种服务一定要抽象到不用去理会实现细节。

很明显,一个典型的例子就是计算机网络协议的设计,高层协议通过服务访问点来使用下层协议所提供的服务,而不必理会下层协议是何种协议。例如TCP是传输层协议,其只需要下层能够将数据传输到端即可,而不关心你是IPv4还是IPv6.

五、接口隔离原则(ISP)

接口隔离原则很简单:

不应该强迫客户依赖于他们不用的方法

因为一旦这些方法发生变化,他们不得不做出相应的改变。

很显然,出现上述情况时,应该对接口进行拆分了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容