设计模式(一)—— 七大设计原则

无论是7个设计原则还是设计模式,完全遵循是很难的,但应有意识尽量遵循。

注意:下面给出的所有例子中,仅是为了体现某种设计原则,没有做到面面俱到,里面可能有其他不符合正常设计思路的地方,比如超类型采用abstract class更好,但我们直接使用的interface。

如果想看面面俱到的例子,可以看《head First》这本书

不要对设计模式或者设计原则感到陌生和害怕,其实在正式前,我们已经在“偷偷”使用了。比如我们的setter方法和getter方法。

一个类明明可以把成员变量定义成public,然后对象p直接p.age这样使用

class People{

        puclic int age; 

}

但是良好的编程习惯告诉我们要让成员变量私有化,只对外提供setter和getter的public接口。你可以把它理解成“最少知道原则”

class People{

        private int age; 

        public setAge(){...}

        public getAge(){...}

}

设计模式都是遵循以下7个原则去设计的:

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒置原则(面向接口编程原则)
  4. 里式替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则
  8. 封装变化(把变与不变的部分分离开,单独封装)

一、单一职责原则

定义:在类的级别上,一个类只负责一项职责;在方法的级别上,一个方法只做一件事。

二、接口隔离原则

定义:一个类对另一个类的依赖应该建立在最小接口上。

注意这里的接口实际上指的是“超类型”,可以是抽象类abstract class,也可以是接口interface。

举个违反接口隔离原则的例子:

 类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D(看了代码就明白了)。

package HeadFirst;

/**
 * 【违反】接口隔离原则的例子
 */
public class InterfaceIsolationTest {
    public static void main(String[] args) {
        //
        B b = new B();
        A a = new A();
        a.test1(b);
        a.test2(b);
        a.test3(b);
        //
        D d = new D();
        C c = new C();
        c.test1(d);
        c.test4(d);
        c.test5(d);
    }
}

interface Interface1{
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}

//类B实现接口Interface1
class B implements Interface1{
    public void operation1(){
        System.out.println("B 实现operation1");
    }
    public void operation2(){
        System.out.println("B 实现operation2");
    }
    public void operation3(){
        System.out.println("B 实现operation3");
    }
    public void operation4(){
        System.out.println("B 实现operation4");
    }
    public void operation5(){
        System.out.println("B 实现operation5");
    }
}

//类D实现接口Interface1
class D implements Interface1{
    public void operation1(){
        System.out.println("D 实现operation1");
    }
    public void operation2(){
        System.out.println("D 实现operation2");
    }
    public void operation3(){
        System.out.println("D 实现operation3");
    }
    public void operation4(){
        System.out.println("D 实现operation4");
    }
    public void operation5(){
        System.out.println("D 实现operation5");
    }
}

//类A 通过接口依赖类B
class A{
    public void dependB1(Interface1 i){
        i.operation1(); //B的operation1
    }
    public void dependB2(Interface1 i){
        i.operation2();//B的operation2
    }
    public void dependB3(Interface1 i){
        i.operation3();//B的operation3
    }
}

//类C 通过接口依赖类D
class C{
    public void dependD1(Interface1 i){
        i.operation1();//D的operation1
    }
    public void dependD4(Interface1 i){
        i.operation4();//D的operation4
    }
    public void dependD5(Interface1 i){
        i.operation4();//D的operation5
    }
}

类A只需要使用类B对接口的1/2/3个实现,但是B却实现了接口全部方法;同样,类C只需要使用类D对接口的1/4/5个实现,但是D也实现了接口的全部方法。对B来说,方法4和5是完全没必要实现的,对D来说方法2和3是完全没必要实现的。

按照【接口隔离】原则,划分出3个【最小接口】:

 

 

 

 三、依赖倒置/控制反转/面向接口编程原则

定义:抽象不应当依赖于细节;细节应当依赖于抽象。理解不了没关系,我们只需要“面向接口编程”就可以了。

PS:控制反转更好理解(见spring)!

做法/核心思想:面向接口编程,而不是面向具体的实现类编程。注意这里的接口实际上指的是“超类型”,可以是抽象类abstract class,也可以是接口interface。

3.1 例子

        什么是依赖?严格意义上,类与类之间的依赖关系指的是,类A在成员方法的形参或者返回值处使用了类B。但是“依赖倒置”的依赖貌似包含了组合关系(因为下面介绍的3种方法中,有两种采用了组合关系),是个更广义的含义?

        什么是被依赖方?Person类在其方法中使用了另一个类——Email类,Person类依赖Email类,Email是被依赖方。

如果Person类还要接收微信消息、QQ消息,就要对receive方法进行重载。所以面向接口编程就是为了解决大量重载的问题,缩短代码量。

 解决:

3.2 依赖倒置的3种使用方法 / 依赖传递的3种方法 / 面向接口编程的3种方法

还是先弄清被依赖方是谁?在下面这个例子中,DriverOne类中使用了车的接口ICar,也就是司机依赖于车,所以被依赖方是【车】

1. 构造方法传递

构造方法传递和setter方法的思路是一样的。直接把被依赖方【车】定义为自己的成员变量。

package HeadFirst;

public class Priciple {
    public static void main(String[] args) {
        NISSAN nissan = new NISSAN();
        DriverOne driver = new DriverOne(nissan);//构造方法传递依赖
        driver.drive();
    }
}

interface ICar{
    public void run();
}

interface IDriver{
    public void drive();
}

class DriverOne implements IDriver{
    ICar car;   //把依赖的车直接定义为成员变量
    DriverOne(ICar car){
        this.car = car; //构造方法里传递依赖
    }
    @Override
    public void drive() {
        car.run();
    }
}

class NISSAN implements ICar{
    @Override
    public void run() {
        System.out.println("尼桑在跑了");
    }
}

2. setter方法传递

        直接把被依赖方【车】定义为自己的成员变量,但不在构造方法里传递依赖,而是再写一个专门传递依赖的setter方法。

package HeadFirst;

public class Priciple {
    public static void main(String[] args) {
        NISSAN nissan = new NISSAN();
        DriverOne driver = new DriverOne();//构造方法传递依赖
        driver.setCar(nissan);
        driver.drive();
    }
}

interface ICar{
    public void run();
}

interface IDriver{
    public void drive();
}

class DriverOne implements IDriver{
    ICar car;   //把依赖的车直接定义为成员变量
    @Override
    public void drive() {
        car.run();
    }

    public void setCar(ICar car) { //setter传递依赖
        this.car = car;
    }
}

class NISSAN implements ICar{
    @Override
    public void run() {
        System.out.println("尼桑在跑了");
    }
}

 3. 接口传递 / 哪个方法用,哪个方法自己通过形参传递

        说是“接口传递”太难理解,不如说是“哪个方法用,哪个方法自己通过形参传递”。

        完整代码如下:

package HeadFirst;

public class Priciple {
    public static void main(String[] args) {
        NISSAN nissan = new NISSAN();
        DriverOne driver = new DriverOne();
        driver.drive(nissan); //通过接口传递依赖
    }
}

interface ICar{
    public void run();
}

interface IDriver{
    public void drive(ICar car); //接口IDriver 依赖于 接口ICar
}

class DriverOne implements IDriver{
    @Override
    public void drive(ICar car) {
        car.run();
    }
}

class NISSAN implements ICar{
    @Override
    public void run() {
        System.out.println("尼桑在跑了");
    }
}

和前面两种传递方式不同的是,不需要将被传递的依赖声明为成员变量。

四、里式替换原则

继承带来的弊端:如果A是祖宗类,B1继承A,B2继承A,C继承B1,D继承C,E继承D......那么一旦我们想要对A发生修改,就要考虑上述所有类。

那么如何正确使用继承?里式替换原则!

定义:在子类中不要重写父类的方法(重写抽象类的抽象方法除外)。通过极限思想来考虑,如果子类重写了父类的所有方法,那子类还继承父类干嘛呢?直接自己造一个类不就好了。

做法:如果B想要继承A并重写A的方法,那再定义一个更高层的类Base,让B和A都继承Base,此时A和B不是父子关系,而是同等地位,现在B想要用A中的东西,就可以聚合、组合、依赖的方式。

 五、开闭原则

定义:对扩展开放,对修改关闭。(对提供类开放,对使用类关闭。比如司机和车,司机是车的使用者,那么司机就是使用类,车是提供类)

做法:如果要增加软件的功能,可以自己添加新的类,但不要动已经写好的代码。

这是7个原则中最核心也是最常用的原则,通俗来说,就是当你进入到一个新团队,无论前面的人写的代码多“垃圾”,都不要动。

其实开闭原则我们在之前依赖倒置的例子中“偷偷”用过。 

开闭原则使用前后,UML图是不变的,我们直接看代码。

public class Priciple {
    public static void main(String[] args) {
        Driver driver = new Driver();
        driver.driveCar(new HongQiCar());
        driver.driveCar(new BenChiCar());
    }
}

class Driver{
    public void driveCar(Car car){
        if(car.CarType == 1){
            HongQiRun();
        }else if(car.CarType==2){
            BenChiRun();
        }
    }
    void HongQiRun(){
        System.out.println("红旗汽车在跑了");
    }
    void BenChiRun(){
        System.out.println("奔驰汽车在跑了");
    }
}

class Car{
    public int CarType;
}

class HongQiCar extends Car{
    HongQiCar(){CarType=1;}
}

class BenChiCar extends Car{
    BenChiCar(){CarType=2;}
}

如果软件需求变更,要临时增加一辆新的车“尼桑”,那我们就要这样修改:

首先新增一个尼桑车类,这是无可厚非的(对扩展开放)。

class NissanCar extends Car{ 
    NissanCar(){CarType=3;}     
}   

然后要将使用类进行如下修改:

 而这个使用类Driver是前人写好的,根据开闭原则,是不应该再打开它进行我们的修改的。

正确写法

public class Priciple {
    public static void main(String[] args) {
        Driver driver = new Driver();
        driver.driveCar(new HongQiCar());
        driver.driveCar(new BenChiCar());
    }
}

class Driver{
    public void driveCar(Car car) {
        car.run();
    }
}

abstract class Car{
    public int CarType;
    abstract public void run();
}

class HongQiCar extends Car{
    @Override
    public void run() {
        System.out.println("红旗汽车在跑了");
    }
}

class BenChiCar extends Car{
    @Override
    public void run() {
        System.out.println("奔驰汽车在跑了");
    }
}

这个时候,我们需要新加一辆尼桑汽车时,只需要添加一个类就好了:
 

class NissanCar extends Car{
    @Override
    public void run() {
        System.out.println("尼桑汽车在跑了");
    }
}

六、迪米特法则/最少知道原则

定义:每个类A都避免不了与其他类B产生关系,但我们让这种关系越小越好。即类A对类B的内部了解的越少越好,如果类A要用到类B,最好只需要调用类B提供的public方法即可。

做法:只允许在一个类的成员变量(组合)、方法参数(依赖)、方法返回值(依赖)处使用另一个类的对象。不允许把另一个类的对象当做自己的局部变量

下面的例子是SchoolManager类的内部:

如果想要写出“蓝色选中”部分的代码, SchoolManager类需要了解CollegeManger内部的逻辑才行,这也就违背了最少知道原则。正确做法是把这一部分逻辑拿到CollegeManger里去写,然后只提供给SchoolManager类一个public接口去调用即可。

 七、合成复用原则

定义:能使用组合、依赖就不使用继承

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 二、设计模式的六大原则 1、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。 2、里氏代换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle) 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。 5、迪米特法则(最少知道原则)(Demeter Principle) 为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle) 原则是尽量使用合成/聚合的方式,而不是使用继承。
针对23种设计模式,分别写了demo并画了类图帮助理解。 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 其实还有两类:并发型模式和线程池模式。 二、设计模式的六大原则 1、开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。 2、里氏代换原则(Liskov Substitution Principle) 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 3、依赖倒转原则(Dependence Inversion Principle) 这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。 4、接口隔离原则(Interface Segregation Principle) 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。 5、迪米特法则(最少知道原则)(Demeter Principle) 为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 6、合成复用原则(Composite Reuse Principle) 原则是尽量使用合成/聚合的方式,而不是使用继承。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值