解读设计原则

概述

设计原则就一本菜谱,告诉我们一道美味的菜应该是什么样的,或者说需要具备什么。但是又没有一个固化或可测量的标准。写代码就和烹饪一样,只有当自己品尝以后才知其味。

1 开闭原则

定义:

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

解读

开闭原则很简单,就是当需求变更的时候,尽量不要修改已有代码。开闭原则是整个设计原则的核心思想。如果当你发现某个需求的改动需要涉及许多地方的代码改动,那么你的代码很有可能是不满足开闭原则的。

小结

单独说开闭原则是没有什么内容可讲的,就像和你说“坚强的人可以克服任何困难”。那么其实我们关心的是怎么才能变得“坚强”。其他的设计原则其实都是围绕着怎么实现开闭原则而进行的。

2 单一职责原则

定义:

单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

解读

通俗一点说,就是一个类不能承担太多的功能。只专心一件事,并且把这件事做好。举一个例子,假如要写一个食堂类。对于客户端来说,食堂核心功能是要能打饭和回收餐具。如果要打饭那么肯定需要有先把菜做好,如果要做菜还要有人去买菜。那么如果这些功能都放在一个类里面就会如下图:
 很明显违反了单一职责原则。为什么?明明就是只负责吃相关职能啊。你都说了是吃相关,那么肯定就不只是一个简单功能。吃前要准备,吃后要收拾。其实作为一个服务,就和我们去食堂吃饭,我只关心怎么打饭,我吃完以后把餐具放哪里。其他的至于菜市从哪里买的,谁炒的都不关心。因此,改良一下

作为食堂,提供多种多样的菜品(如川菜、粤菜)。如果让一个厨师即炒粤菜又炒川菜又违背了单一职责原则。因此再改一下:

单一职责好处就是: 可以降低类的复杂度,提高类的可读性,降低变更引起的风险降低。变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

小结

其实单一职责不仅仅是针对一个类的设计,往小的说一个方法、往大的说一个模块都应该满足单一职责原则。只是怎么去确定职责及其范围是需要根据具体的场景来确定。

3 里氏代换原则

定义:

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

解读

里氏代换原则核心就是要满足继承体系,能用父类的地方,那么其任何子类也可以正确调用。否则,就不应该做为其子类(或者父类的定义就不正确)。例如鸟是一个父类,那么所有的鸟都应该是卵生。但是如果父类定义了方法fly()就不一定了,因为有的鸟不会飞的。这个就不满足里氏替换原则。从代码层面上来说:如果一个类实现一个接口,那么就应该实现该接口的所有方法。比较经典的案例就是Spring的CacheManager的接口。

小结

如果你理解(或者使用过)面向对象语言,那么里氏替换原则理解起来就十分简单。例如java在编译的时候就会检查一个程序是否符合里氏替换原则。虽然很简单,但是这也是实现开闭原则的基本条件。试想,当扩展一个接口的时候,发现扩展类在实现该接口的方法不能正确调用,那么这个该扩展也是没有任何意义的。

4 依赖倒转原则

定义:

依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

解读

上面定义中的抽象可以简单的理解为接口,细节就是接口的实现类。也就是说,无论是定义变量、方法参数类型还是方法返回都尽量使用抽象接口,而不是返回具体的实现类。这样做的目的就是方便以后扩展(也就是实现开闭原则的手段)。还是前面的食堂为例,假如一开始的时候只有MarketA卖菜,采购员就只有去哪里卖菜。也许一开始的代码就会这样写:

class MarketA {
    public String name() {
        return ("MarketA");
    }
}

class Buyer {
    public void buy(MarketA marketA) {
        System.out.println("采购员在" + marketA.name() + "买菜");
    }
}

public void CanteenBuyFood() {
    Buyer buyer = new Buyer();
    buyer.buy(new MarketA());
}

  

突然有一天,MarketB开业,并且每周一的价格比MarketA便宜,因此,选择周一在MarketB买菜。

class MarketA {
    public String name() {
        return ("MarketA");
    }
}

class MarketB {
    public String name() {
        return ("MarketB");
    }
}

class Buyer {
    public void buyA(MarketA marketA) {
        System.out.println("采购员在" + marketA.name() + "买菜");
    }

    public void buyB(MarketB marketB) {
        System.out.println("采购员在" + marketB.name() + "买菜");
    }
}

public void CanteenBuyFood() {
    Buyer buyer = new Buyer();
    // 其实这也也不满足单一职责原则,即要选择菜市场,又要派采购员买菜。
    if(to is monday){
        buyer.buyA(new MarketA());
    }else{

        buyer.buyB(new MarketB());
    }
}

  

突然又有一天,MarketC,MarketD...MarketCX 开业了,并且....好吧,是不是发现这样写下去什么时候是个头啊。因此我们需要进行代码重构,使其满足开闭原则。

interface Market {
    String name();
}

class MarketA implements Market {
    public String name() {
        return ("MarketA");
    }
}

class MarketB implements Market {
    public String name() {
        return ("MarketB");
    }
}

interface Buyer {
    public void buy(Market market);
}
//省略Buyer的子类

public void CanteenBuyFood() {
    Market market = selectMarket();
    Buyer buyer = selectBuyer();
    buyer.buy(market);
}

private Buyer selectBuyer() {
    //根据实际情况选择采购员
}

private Market selectMarket() {
    // 根据情况选择市场
}

  

重构后代码是不是无论加多少market,只要修改selectMarket()的方法。无论加多少采购员,只要修改Buyer.getBuyer()中的代码。

小结

可以看出,在代码重构过程中,在大多数情况下开闭原则、里氏代换原则和依赖倒转原则这三个设计原则会同时出现。开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,他们目的都是为了代码的扩展性,只是分析问题时所站角度不同而已。

5 接口隔离原则

定义:

接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

解读

接口隔离就是定义一个接口的时候不要定义得太而广,而是把他们分割成一些更细小的接口。一般不满足单一职责原则都不满足接口隔离原则,这样的例子很多,就不多说明了。但反过来却不一定,举一个例子(往往原则的例子举反例是最清晰的)。还是买菜的问题,如果我们定义一个市场,在市场里面买肉、买蔬菜、买水果....),往往一开始我们定义接口的时候是按照一个样例来定义接口,比如上面就是按照一个超级市场来定义的接口,其实这样做是很正常的情况。

interface Market {
    void buyMeat();

    void buyVegetables();
}

class SuperMarket implements Market {

    public void buyMeat() {
        System.out.println("买肉");
    }

    public void buyVegetables() {
        System.out.println("买菜");
    }
}

  

这样定义接口在一开始是没问题,但是随着业务扩展,如果现在存在另外一个市场,他只卖菜,不卖肉。。那么该类在 buyMeat()下面怎么办?当你发现你实现一个接口的时候,某个需要实现的方法你没办法去实现,这种情况也算不满足接口隔离的原则了。这时候就需要进行重构,把接口进一步细化,例如,把Martet细化为MeatMarket和VegetableMarket。

interface Market {
    //其他共有方法
}

interface MeatMarket extends Market{
    void buyMeat();
}

interface VegetableMarket extends Market{
    void buyVegetables();
}

class SuperMarket implements MeatMarket,VegetableMarket {

    public void buyMeat() {
        System.out.println("买肉");
    }

    public void buyVegetables() {
        System.out.println("买菜");
    }
}

class SmallMarket implements VegetableMarket {

    public void buyVegetables() {
        System.out.println("买菜");
    }
}

  

上面的实例代码仅仅是为了说明接口隔离原则,

小节

接口隔离原则核心思想就是细化接口,提高程序的灵活性。但细化到什么程度却没有具体的度量,接口不能太小,如果太小会导致系统中接口泛滥,反而不利于维护。因此如何把握这个读就是经验了。

迪米特法则

定义

迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

解读

迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等

一个类只与他的朋友类进行交互

所谓一个类的朋友,就是该类

  1. 当前对象本身(this);
  2. 以参数形式传入到当前对象方法中的对象,或者返回的方法返回的对象
  3. 当前对象的成员对象;

从代码层面上讲,当该类的所有方法体为空的时候,该类所依赖的类就是朋友类 所谓只与他朋友类进行交互意思就比较好理解了,就是不能直接调用他朋友的朋友方法。也就是在中,食堂服务\=\=朋友==>食堂后勤\=\=朋友==>厨师,我们不能直接在食堂服务中直接调用让厨师去炒菜。食堂服务类要与厨师类通信,也是必须要有食堂后勤类作为中介。从代码层面上来说,一般 A.getB().getC().xxxMethod() 往往都是破坏迪米特法则。

即使是朋友也要保持距离

一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。例如,

class A {
    public void step1(){
        //do something
    }

    public void step2(){
        //do something}
    }

    public void step3(){
        //do something}
    }
}
class M {
    public void someCall(A a) {
        a.step1();
        a.step2();
        a.step3();
    }
}

  

从代码里面看,A确实是M的朋友,但是,M和A太"亲密"了,如果以后需要在调用step2的之前调用一个check2()的方法,就必须要修改M中的方法,如果这个调用方式大量出现在工程中,就会引起扩散。如果我之前是这么设计的交互的方式就不会存在这个问题。也就是说,朋友之间的交互不要太"亲密"。同时作为别人朋友,最好能提供“一站式服务”。

class A {
    private void step1(){
        //do something
    }

    private void step2(){
        //do something}
    }

    private void step3(){
        //do something}
    }

    public void exe(){
        step1();
        step2();
        step3();
    }
}
class M {
    public void someCall(A a) {
       a.exe();
    }
}

  

小节

迪米特发展的目的就是降低系统的耦合度,使类与类之间保持松散的耦合关系。和接口隔离原则一样,迪米特法则也是一个无法进行度量。过度使用迪米特法则,也会造成系统的不同模块之间的通信效率降低,这就直接导致了系统中存在大量的中介类。因此,如何使用迪米特法则还是那句话,根据经验。

总结

单一职责原则告诉我们实现类的功能不要太多。里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则总的大纲,要对扩展开放,对修改关闭。
设计原则只是给我们一些指导性的意见,在实际工作中往往要根据实际情况来判断。就如开篇所说的那样,往往菜谱往往给我们的意见都是“少许”,“适量”,而真正要烹饪出一道美味还需要我们自己不断去积累和调整。

时间: 2024-10-17 06:22:13

解读设计原则的相关文章

设计原则在手机摄影中的运用

作者:Vamei 出处:http://www.cnblogs.com/vamei 转载请保留这段声明. 为了给在校的最后一年留下纪念,最近我总是拿着手机溜达到校园的角落拍照.手机拍照很容易分享.我渐渐发现,那些最受朋友喜爱的照片,无意识中符合了某些设计原则.这些原则交错游走,不断遵循和打破,最终定格成下面的一张张照片. 统一 如果说艺术是对生活的提炼,那么摄影就是对视觉的提炼.所谓的“提炼”,就是从日常看到的东西中做减法,把一个画面中的所有元素统一到同一个主题上来.我们需要不断去掉画面中的杂乱元

浅谈Java六大设计原则

笔者刚接触设计原则的时候,觉得一头雾水,不知道他有什么用.在经历了一段时间的代码加上了解Java设计模式之后.笔者忽然觉得自己以前写的代码就是一堆*.所以,笔者认为设计原则和设计模式对于软件编程设计(非码农)来说是至关重要的事情.相信很多学习编程的人,和我有同样的感受. 我对设计模式和设计原则的理解是:如果把程序员比作武侠,那么设计模式就是修炼内功的易筋经,设计原则就是修炼内功的心法总纲,而具体的技术实现(代码编写)就是罗汉拳.如果你只想自保,那么会罗汉拳就可以了(能够用代码实现功能),不过如果

设计原则二:空间和图底关系

设计原则系列文章的第二篇,主要讲留白空间和图底关系.这两个原则都来源于格式塔理论,可见格式塔的重要性.分享知识,是我们一直坚持的理念并会一直坚持下去. 设计原则二:空间和图底关系 如果你见过平面设计在画布上绘制图形的过程,那么你紧紧是见到你工作的一部分内容.画布上的负空间和我们在画布上绘制元素的正形空间同样重要. 设计就是处理形状和空间两者之间关系.为了更有效的利用空间,首先你必须意识到它,并且学会看它.就是学会如何看空间中的图形,空间的形式和空间与空间之间是如何交流的.这是初学者要学习和了解的

浅析交互设计原则:设计应以人为本

为什么需要计原则? 设计原则其实就是对一些设计过程中基于人类的认知规律对设计做出的一些指导性原则,并且对已经成为行业共识的设计经验做个总结,用来指导设计师界定问题.提高效率. 经常用的设计原则有哪些? 先来抛一个老D在早年刚接触交互设计的时候最为大家所认可的几条设计原则: 1.可学习性 目标用户在已有的知识和经验基础上,能正确理解产品界面,无需要思考而一目了然:或者是用户通过自己的学习,借助提示或帮助说明,能够理解产品界面.则界面具有了可学习性. 可学习的内容包括:明确当前所在位置,知道当前能干

设计模式设计原则

设计原则详解 设计模式存在的根本原因是为了代码复用,增加可维护性. 开闭原则:对扩展开放,对修改关闭 里氏转换原则:子类继承父类,单独掉完全可以运行 依赖倒转原则:引用一个对象,如果这个对象有底层类型,直接引用底层. 接口隔离原则:每一个接口应该是一种角色 合成/聚合复用原则:新的对象应使用一些已有的对象,使之成为新对象的一部分 迪米特原则:一个对象应对其他对象有尽可能少的了解 综述:站在巨人的肩膀上整体HOLD系统架构 设计模式概念解读 1.设计模式概念文字解读 设计模式是一套被繁复使用,思想

喵星之旅-沉睡的猫咪-面向对象的设计原则

一.设计原则是什么? 有句话叫“人民群众是历史的创造者”,他的意思我理解为任何的理论都是基于具体的客观展现总结出来的,没有人民创造的既定事实,就无法出现任何的有理有据的理论模型. 对于面向对象的软件设计,最著名的一本书就应该是当年gof的那一本<设计模式:可复用面向对象软件的基础>.设计模式是面对具体问题,进行抽象分类,然后总结出来的行之有效的解决方案,就像人去创造历史.设计原则是进一步研究这些解决方案,进一步抽象出的指导思想. 如果把设计模式比喻成传统数学,即“1+1=2”的那套理论,那么设

Java程序员应该了解的10个面向对象设计原则

面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorator.Observer这样的设计模式,而不重视面向对象的分析和设计.甚至还有经验丰富的Java程序员没有听说过OOPS和SOLID设计原则,他们根本不知道设计原则的好处,也不知道如何依照这些原则来进行编程. 众所周知,Java编程最基本的原则就是要追求高内聚和低耦合的解决方案和代码模块设计.查看Ap

Hbase中rowkey设计原则

Hbase中rowkey设计原则 1.热点问题 在某一时间段,有大量的数据同时对一个region进行操作 2.原因 对rowkey的设计不合理 对rowkey的划分不合理 3.解决方式 rowkey是hbase的读写唯一标识 最大长度是64KB. 4.核心原则 设计必须按照业务需求进行设计 5.长度原则 经验:10~100字节可以 官方:16字节,因为操作系统时8字节进行存储 6.散列原则 划分region是按照rowkey的头部进行划分. 有几种方式: )组合字段 id+timestamp )

设计原则之接口隔离原则

segregate   v.隔离 se 蛇  gre green格林  gate门 蛇被格林用门隔离了. 设计原则之接口隔离原则 动机:         客户不应该被强制实现他们不用的方法.应该用多个小的接口代替庞大功能全的接口. 结论:        该原则在代码设计的时候就要考虑.可以使用适配器模式将胖接口隔离. Bad Example:    缺点:         1.如果新增一个robot机器人工人,那么eat方法就是多余的了. // interface segregation pri