设计模式6个基本原则学习和总结

网上这个相关内容有很多,但是大都说的太复杂了,所以这里我想用一篇来对这六个原则做以概括和总结,本文有部分内容摘自网络,由于本人水平有限,错误在所难免,如果我个人的理解有什么不对、不到位的地方,恳请各位高手指出。

1、单一职责原则(SRP:Single Responsibility Principle)

关键句:一个类只负责一个职责

看例子理解:

class Animal{
    public void breathe(String animal){
        System.out.println(animal+"用肺呼吸");
    }
}
public class Client{
    public static void main(String[] args){
        Animal animal = new Animal();
        animal.breathe("牛");
        animal.breathe("羊");
        animal.breathe("猪");
    }
}

运行结果不必多说,但是后面我们发现这个Animal类还会包括鱼,animal.breathe(“鱼”),显然常见的鱼并不是通过肺来呼吸的,这时我们就需要修改了。这里也就发生了职责扩散,职责扩撒就是由于某种原因,职责需要细分为职责1和职责2

我们可能会这样改:

(1)修改Animal类的breathe方法:(不建议的修改方式)

    public void breathe(String animal){
        if("鱼".equals(animal)){
            System.out.println(animal+"用鳃呼吸");
        }else{
            System.out.println(animal+"用肺呼吸");
        }
    } 

上面这种修改方式是很简单,但是存在进一步的风险,假如往后的某一天,我们知道了某些鱼还会用其他方式呼吸,这样我们又要进一步修改这个类,这时我们的修改可能就会影响到 牛 羊 等呼吸方式。这也违背了单一职责原则,所以这种方式不可取。

(2)增加Animal类新的breathe方法:(根据实际情况确定是否使用)

class Animal{
    public void breathe(String animal){
        System.out.println(animal+"用肺呼吸");
    }  

    public void breathe2(String animal){
        System.out.println(animal+"用鳃呼吸");
    }
}  

上面修改方式,在方法级别符合单一职责原则,因为它并没有动原来方法的代码。类级别是违背单一职责原则。这种方式在类中方法足够少的情况下可以考虑使用。

(3)细分Animal类:(标准的,不违反单一职责的方式)

class Terrestrial{
    public void breathe(String animal){
        System.out.println(animal+"用肺呼吸");
    }
}
class Aquatic{
    public void breathe(String animal){
        System.out.println(animal+"用鳃呼吸");
    }
} 

上面修改的方式,修改花销是很大的,除了将原来的Animal类分解之外,还需要修改客户端。即:

        Terrestrial terrestrial = new Terrestrial();
        terrestrial.breathe("牛");
        Aquatic aquatic = new Aquatic();
        aquatic.breathe("鱼"); 

所以,上面(2)(3)修改方式需要综合项目的复杂程度等选择使用(如何选择使用上面已经有说到)

2、里氏替换原则(LSP:Liskov Substitution Principle)

关键句:子类可以扩展父类的功能,但不能改变父类原有的功能

这个原则主要是针对继承而言的,因为继承往往有这样的含义:父类中已经实现的方法,其实也就是针对该类部分行为的描述和定义。而若子类对这个方法进行任意修改,那么就会造成继承体系的破坏。

实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的代码的可复用性会比较差。

如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

3、依赖倒置原则(DIP:Dependency Inversion Principle)

关键句:细节应该依赖抽象,面向接口编程

看例子理解这个原则:

class Book{
    public String getContent(){
        return "很久很久以前有一个阿拉伯的故事……";
    }
}  

class Mother{
    public void narrate(Book book){
        System.out.println("妈妈开始讲故事");
        System.out.println(book.getContent());
    }
}  

public class Client{
    public static void main(String[] args){
        Mother mother = new Mother();
        mother.narrate(new Book());
    }
}

上面的例子有一个Mother类,有一个Book类,Book类作为一个参数传入到Mother类中,这样,Mother类就完成了对Book类的读取。

但是这时候,要增加一个需求,Mother要读报纸,与Book类相似,Newspaper类如下:

class Newspaper{
    public String getContent(){
        return "林书豪38+7领导尼克斯击败湖人……";
    }
}

这时候我们就发现,Mother类的narrate方法只接受Book的对象,并不会读Newspaper,所以我们考虑如下修改方式:

(1)Mother类增加narrate1方法,传入Newspaper。

绝对的坑爹设计,以后如果还有 杂志、小说要读,那是不是会有更多方法需要增加,Mother类需要不断修改。

(2)面向接口编程,引入接口IReader。

interface IReader{
    public String getContent();
}

Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为:

class Newspaper implements IReader {
    public String getContent(){
        return "林书豪17+9助尼克斯击败老鹰……";
    }
}
class Book implements IReader{
    public String getContent(){
        return "很久很久以前有一个阿拉伯的故事……";
    }
}  

class Mother{
    public void narrate(IReader reader){
        System.out.println("妈妈开始讲故事");
        System.out.println(reader.getContent());
    }
}  

public class Client{
    public static void main(String[] args){
        Mother mother = new Mother();
        mother.narrate(new Book());
        mother.narrate(new Newspaper());
    }
}

遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。

4、接口隔离原则(ISP:Interface Segregation Princeple)

关键句:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上

这个原则解决这样一个问题:

类A通过接口 I 依赖类B,类C通过同样的接口 I 依赖类D。这是接口 I 有类B和类D的方法,但是对于类B和类D他们彼此并不需要对方的方法。这时,接口 I 的就过于臃肿,因此需要拆分。

这个原则比较好理解,这里不再用例子解释。需要记住的就是建立单一接口,不要建立庞大臃肿的接口,适度细化接口,接口中的方法尽量少。

5、迪米特法则(Law of Demeter)/最少知道原则(LKP: Least Knowledge Principle)

关键句:一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合

这个原则也可以这样理解,类仅与直接的朋友通信。(直接的朋友包括:成员变量、方法参数、方法返回值)

举个例子,现在有如下程序:

//总公司员工
class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

//分公司员工
class SubEmployee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

现在需要输出整个公司员工的Id,我们可能会这样写代码:

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //为分公司人员按顺序分配一个ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }
}

class CompanyManager{
    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            //为总公司人员按顺序分配一个ID
            emp.setId("总公司"+i);
            list.add(emp);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManager sub){
        List<SubEmployee> list1 = sub.getAllEmployee();
        for(SubEmployee e:list1){
            System.out.println(e.getId());
        }

        List<Employee> list2 = this.getAllEmployee();
        for(Employee e:list2){
            System.out.println(e.getId());
        }
    }
}

在分公司SubCompanyManager类中创建方法getAllEmployee()

在总公司CompanyManager类中创建方法getAllEmployee()和printAllEmployee()。

根据迪米特法则,ComanyManager与Employee是直接朋友,但是与SubEmployee并不是直接朋友。这样总公司与分公司逻辑上是耦合的了。所以这种方式不可取,需要修改:

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //为分公司人员按顺序分配一个ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }
    public void printEmployee(){
        List<SubEmployee> list = this.getAllEmployee();
        for(SubEmployee e:list){
            System.out.println(e.getId());
        }
    }
}

class CompanyManager{
    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            //为总公司人员按顺序分配一个ID
            emp.setId("总公司"+i);
            list.add(emp);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManager sub){
        sub.printEmployee();
        List<Employee> list2 = this.getAllEmployee();
        for(Employee e:list2){
            System.out.println(e.getId());
        }
    }
}

上面方法就有效的降低了类之间耦合,仅与直接的朋友进行了通信。

6、开闭原则(OCP:Open Closed Principle)

关键句:类、模块、功能应该对扩展开放,对修改关闭

开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;

开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。

之前提到的里氏代换原则、依赖倒置原则、接口隔离原则,还有我们知道的抽象类、接口等等,都可以看作是开闭原则的实现方法。

时间: 2024-11-03 05:34:52

设计模式6个基本原则学习和总结的相关文章

设计对象的一些基本原则(学习自设计模式之禅)

1.单一职责原则(srp):就是设计一个对象,对象的职责要单一. 比如设计用户类,将用户的行为和用户的属性分成两个接口,继承的方式设计类. 还有一个srp的解释是:there is no more than one reason for a class to change 但srp的原则有可能把握的过细,导致类太散.所以也不可以走极端. 2.里式替换原则:代码中使用父类的地方都可以用他的子类来代替. 个人理解,子类里有全部的父类的方法(继承来的或者重载过了),若重载了方法,实现的逻辑肯定和父类不

《邓哥奇遇记9》—— 设计模式之六大基本原则

我们在学习.开发或面试时经常会听到设计模式,很多同学也多多少少能说出一点关于设计模式的东西来,但是很多同学却一直无法理解设计模式的精髓,那么今天开始我们就来聊聊设计模式~ 我们依旧提出几个问题: 01设计模式和设计原则是什么关系? 答:所有的设计模式都是遵循设计原则的,不能违反设计原则. 02设计原则的核心思想是什么? 核心思想只有两条:减少复杂度(让一个复杂的逻辑变成多个简单的逻辑),降低耦合度(让模块之间的关联减少,使得模块之间更独立.更清晰) 那么今天我们就来用邓哥的故事来和大家聊聊设计模

设计模式(二)学习----动态代理

动态代理:动态代理是指在实现阶段不需要关心代理谁,而在运行阶段才指定代理哪一个对象.Spring AOP采用的核心思想就是动态代理设计模式.  下面看动态代理的UML类图: 下面思考问题:invocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的? 动态代理类: package com.lp.ecjtu.DynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect

设计模式——抽象工厂模式学习

要想正确的理解设计模式,首先必须明确它是为了解决什么问题而提出来的. 抽象工厂设计模式概念: 针对抽象工厂这个设计模式,我查找了不少资料,感觉只有涉及产品级别和产品族的才是理解了抽象工厂设计模式的精髓,工厂方法模式针对的是一个产品等级结构:而抽象工厂模式针对的是多个产品等级结构.有些观点认为抽象工厂模式是为了解决客户端代码与工厂类的耦合问题,我认为这种观点的解决方案只是简单工厂模式的一个应用,而这种观点认为的抽象工厂模式是: 工厂模式+简单工厂模式=抽象工厂模式,这是不正确. 针对的问题: 针对

设计模式6大基本原则之(二)

上一篇博客中介绍了设计模式6大基本原则的前三个,这篇博客将着重介绍剩下的三个基本原则. 里氏代换原则(Liskov Substitution Principle) 子类必须能够替换掉它的父类.也就是说子类可以扩展父类的功能,但不能改变父类原有的功能. 遵循里氏代换原则的好处? 由于子类型的可替换性才使得使用父类的模块在无需修改的情况下就可以扩展. 打个比方说,香蕉是水果的一种,香蕉具有水果的特性.假如有一天我们需要再增加苹果.橘子.苹果拥有水果 的特性,由于都是继承于水果,除了更改实例化的地方程

【《软件设计模式与体系结构》学习笔记】软件设计模式概论

[<软件设计模式与体系结构>学习笔记] 软件设计模式的概念 软件设计模式是对软件设计经验的总结,是对软件设计中反复出现的设计问题的已被验证的成功解决之道.大量的软件设计模式都是之前从事软件设计开发的前人经过大量的实践而摸索出来的,用于帮助后来者快速高效且高质从事软件开发的. 软件设计模式的要素 软件设计模式一般会包含四个基本要素: 模式名称:此种设计模式的名字: 问题:是设计者所面临的设计场景,也就是此种设计模式所适用的情况: 解决方案:描述设计细节,通常会采取UML等图示的方式来进行设计模式

设计模式(一)学习----策略模式

策略设计模式:定义一组算法,将每个算法都分装起来,并使他们之间可以互换. 策略模式就是使用的面向对象思想中的继承和多态的机制 策略模式的通用类图: Context类:Strategy类,并且和Strategy类是整体和个体的关系,即聚合关系.对策略角色个体进行封装. Strategy接口:定义这个策略或算法必须有的方法和属性. ConcreteStrategy1,ConcreteStrategy2具体策略角色实现类.实现抽象策略中的方法,该类中含有具体的算法. 上图变成Java代码: 抽象的策略

面向对象设计模式5大基本原则

"宇宙万物之中,没有一样东西能像思想那么顽固."        一爱默生 首先明确模式是针对面向对象的,它的三大特性,封装.继承.多态. 面向对象设计模式有5大基本原则:单一职责原则.开发封闭原则.依赖倒置原则.接口隔离原则.Liskov替换原则. 而设计模式都是在面向对象的特性以及5大基本原则的基础上衍生而来的具体实现. 1.单一职责原则(SRP): 1.1,SRP(Single Responsibilities Principle)的定义:就一个类而言,应该仅有一个引起它变化的原因

JavaScript设计模式之观察者模式(学习笔记)

设计模式(Design Pattern)对于软件开发来说其重要性不言而喻,代码可复用.可维护.可扩展一直都是软件工程中的追求!对于我一个学javascript的人来说,理解设计模式似乎有些困难,对仅切图.做少量交互效果的FE甚至可能不会用到,但是当你开始使用Angular/Backbone等框架的时候,就无法避免设计模式.MVC/MVVM这些东西了(反正我是伤脑筋). 我学设计模式是刚开始接触编程大概三个月的时候,看一本书<大话设计模式>,里面用C#语言来写,我很无语,因为强类型的编程语言对于