设计模式学习-6大设计原则

六大设计原则

名称 概念
开闭原则 对拓展开发,对修改关闭
里氏代换原则 任何基类出现的地方,子类一定可以出现
依赖倒转原则 针对接口编程,而不是实体类
接口隔离原则 单一责任原则,通过接口来降低耦合
迪米特法则 一个实体尽量少的与其他实体发生相互作用的关系
合成复用原则 尽量使用和合成/聚合的方式,而不是使用继承

开闭原则

1. 个人理解

1. 开发原则是软件开发的终极目标,做好其他5中设计原则就能实现开闭原则
2. 开闭原则的核心是:抽象构建框架,用实现拓展细节
3. 可以理解为对基类继承,子类根据需求增加方法,而不必改变基类代码。
根据需求来拓展实现类,而不改变接口的代码。
开闭原则要懂得对哪些进行变化(子类,实现类),哪些是不变的(接口,基类)。

2. 代码示例

2.1 如何使用开闭原则

1. 抽象约束
2. 封装变化
~~~~
将相同的变化封装到一个接口或抽象类中,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同变化出现在同一个接口或抽象类中。

2.2 实现代码

//鸟接口
public interface Bird {
    public void fly();
    public void call();
}
/红鸟
public class RedBird implements Bird {
    public void fly() {
        System.out.println("我会飞!");
    }

    public void call() {
        System.out.println("嘎嘎叫!");
    }
}
//鸟有了新的本领-游泳,开闭原则:不能更改鸟接口的代码,在鸟接口的实现类进行拓展开发
public class EnhanceBird implements Bird {
    //构造函数
    public EnhanceBird(){
        super();
    }
    public void fly() {
        System.out.println("我会飞!");
    }

    public void call() {
        System.out.println("叽叽的叫!");
    }

    public void swim(){
        System.out.println("我还会游泳!");
    }
}

//测试,查看结果
public class Test {
    public static void main(String[] args) {
        System.out.println("我是原始版的鸟!");
        Bird bird=new RedBird();
        bird.fly();
        bird.call();

        System.out.println("我是加强版的鸟,加入了新的本领!!!");
        EnhanceBird enhanceBird1 =new EnhanceBird();
        enhanceBird1.call();
        enhanceBird1.fly();
        enhanceBird1.swim();

    }
}

//结果如下
我是原始版的鸟!
我会飞!
嘎嘎叫!
···············································
我是加强版的鸟,加入了新的本领!!!
叽叽叫!
我会飞!
我还会游泳!

3. 总结

  1. 上面这段代码是我对开闭原则的理解,就是不管接口设计的是是否很完善,当有新的需求时都不能改变接口的代码。上面代码中只有RedBird实现Bird接口,但是在实际开发中一个接口可能被几十上百个类实现,改接口这些类都要进行改变。
  2. 因此对于要增加就在接口的实现类中进行拓展。这样只需要改一个类就行了,并且以后的类需要这个拓展(swim)时,直接继承增强类就可以了。
  3. 其实也可以定义一个接口,接口继承Bird接口,在该接口中添加swim()方法。以后的实现类都实现该新定义的接口就行。
  4. 该代码示例是有缺陷的,在抽象约束这一点上并没做好,导致之后要增加swim()方法,该方法应该是写在Bird的。正真的拓展,是对接口中的方法进行不同的实现。如代码例子中,鸟的叫声有几种,“叽叽叫"和"嘎嘎叫”。这才是"封装变化"的正确理解

2. 里氏代换原则

1. 个人理解

  • 该设计原则是使用继承的基础,什么时候使用继承,什么时候不能使用继承。
  • 继承必须确保超类所拥有的性质在子类中仍然成立

1.1 里氏替换原则的作用

  • 里氏替换原则是实现开闭原则的重要方式之一。
  • 它克服了继承中重写父类造成的可复用性变差的缺点。
  • 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

1.2 里氏替换原则的实现方法

  • 子类继承父类时尽量不重写父类的方法,因为重写会破坏整个继承结构。
  • 如果必须要重写父类方法,那么就要破坏原来的继承关系,重新设计新的继承关系,满足子类不重写父类方法来满足要求。

2. 代码示例

//父类-鸟
public class Bird {
    public void fly(){
        System.out.println("我会飞!");
    }

    public void call(){
        System.out.println("我会嘎嘎叫!");
    }
}

//鸟的子类-黑鸟
public class BlackBird extends Bird {

}

//鸟的子类-红鸟~~~~
public class RedBird extends Bird {
    @Override
    public void call() {
        System.out.println("我呀叽叽叫!");
    }
}
//运行结果
黑鸟的叫声!~~~~~~~~~~~~~~~~~~~
我会叽叽叫!

红鸟的叫声!~~~~~~~~~~~~~~~~~~~
我会嘎嘎叫!

 由于红鸟不会"叽叽叫",所以重写了父类call()方法,这样就破坏了里氏替换原则
重新设计继承关系

//animal
public class animal {
    public void fly(){
        System.out.println("我会飞");
    }
}

//Bird继承animal
public class Bird extends animal {
    public void call(){
        System.out.println("我会嘎嘎叫!");
    }
}

//黑鸟继承Bird
public class BlackBird extends Bird {

}

//红鸟继承animal
public class RedBird extends animal {
   public void call(){
       System.out.println("我会叽叽叫!!");
   }
}



public class liskovTest {
    public static void main(String[] args) {
        RedBird redBird=new RedBird();
        Bird blackBird=new BlackBird();
        System.out.println("黑鸟的叫声!~~~~~~~~~~~~~~~~~~~");
        blackBird.call();
        System.out.println("红鸟的叫声!~~~~~~~~~~~~~~~~~~~");
        redBird.call();
    }
}

//结果
黑鸟的叫声!~~~~~~~~~~~~~~~~~~~
我会嘎嘎叫!
黑鸟的叫声!~~~~~~~~~~~~~~~~~~~
我会叽叽叫!!


3. 总结

  • 这个代码例子有不恰当之处,不具现实性。在这个例子中,红鸟已经不是鸟的范畴了。
  • 我所表达思想已经到位了,只要子类重写父类方法,就要重新设计继承,还要进行进一步的抽象。

3. 依赖倒转原则

1. 个人理解

  • 就是面向接口编程,而不是具体实现类
  • 将能够抽象定义的都写在接口中,而不是实现类中。
  • 其实就是实现类实现接口,在定义变量是通过向上转型将实现类对象赋值给接口对象,在进行操作时时对接口对象进行操作,而不是具体的实现类。

1.1 依赖倒转原则的作用

  • 依赖倒置原则可以降低类间的耦合性。
  • 依赖倒置原则可以提高系统的稳定性。
  • 依赖倒置原则可以减少并行开发引起的风险。
  • 依赖倒置原则可以提高代码的可读性和可维护性。

1.2 依赖倒转的实现

  • 每个类尽量提供接口或抽象类,或者两者都具备。
  • 变量的声明类型尽量是接口或者是抽象类。
  • 任何类都不应该从具体类派生。
  • 使用继承时尽量遵循里氏替换原则。

2. 代码示例

//鸟接口
public interface Bird {
    public void fly();
    public void call();
    public void run();

}
//红鸟-实现鸟接口
public class RedBird implements Bird {

    public void fly() {
        System.out.println("我是红鸟,我会高空飞翔!!");
    }

    public void call() {
        System.out.println("我是红鸟,我会叽叽叫!");
    }

    public void run() {
        System.out.println("我是红鸟,我会慢跑!");
    }
}

//黑鸟-实现鸟接口
public class BlackBird implements Bird {
    public void fly() {
        System.out.println("我是黑鸟,我会低空飞翔!");
    }

    public void call() {
        System.out.println("我是黑鸟,我会嘎嘎叫!");
    }

    public void run() {
        System.out.println("我是黑鸟,我会快速跑!");
    }


//创建鸟对象,红鸟和黑鸟通过向上转型给接口

public class InverseDependentTest {
    public static void main(String[] args) {
        Bird bird= new RedBird();
        Bird bird1=  new BlackBird();
        System.out.println("红鸟 ~~~~~~~~~~~~~~~~~~~~~~~");
        bird.call();
        bird.fly();
        bird.run();
        System.out.println("黑鸟 ~~~~~~~~~~~~~~~~~~~~~~~");
        bird1.call();
        bird1.fly();
        bird1.run();
    }
}
//
在执行过程中通过动态绑定,来确定执行哪个实现类的方法。

//执行结果
红鸟 ~~~~~~~~~~~~~~~~~~~~~~~
我是红鸟,我会叽叽叫!
我是红鸟,我会高空飞翔!!
我是红鸟,我会慢跑!
黑鸟 ~~~~~~~~~~~~~~~~~~~~~~~
我是黑鸟,我会嘎嘎叫!
我是黑鸟,我会低空飞翔!
我是黑鸟,我会快速跑!

3. 总结

  • 面像接口编程,定义好接口之后,具体的细节留给实现来完成。

  • 代码中对接口进行操作,这样系统更加稳定,因为接口很稳定。然而实现类时多样的,其存在的不稳定性大于接口。

4. 接口隔离原则

1. 个人理解

  • 一个类对另一个类的依赖应该建立在最小的接口上
  • 如果接口太臃肿,使接口发生变化的因素就会有很多,接口时常发生变化,那么依赖该接口的类也要随之变化。这样不能时耦合度增加
  • 只能有一个因素造成接口的变化,这样时接口内达到高内聚,与外部达到低耦合。
  • Java语言中,一个类可以实现多个接口,这样就可以将一个大的接口拆分成多个小的接口。实现类再实现这些接口

2. 代码示例


//商品接口
public interface Goods {
    public void getGoodName();
    public void getStock();

}

//物流接口

public interface Logistics {
    //物流接口
    public void getLogisticsConpany();
}

//订单实现类
public class Order implements Goods, Logistics {
    public void getGoodName() {
        System.out.println("得到商品名称。");
    }

    public void getStock() {
        System.out.println("得到商品库存。");
    }

    public void getLogisticsConpany() {
        System.out.println("得到物流公司。");
    }
}

//

public class oneInterfaceTest {
    public static void main(String[] args) {
        Goods goods= new Order();
        Logistics logistics=new Order();
        goods.getGoodName();
        goods.getStock();

        System.out.println("~~~~~~~~~~~~~~~");
        logistics.getLogisticsConpany();
    }
}

//结果
~~~~~~商品接口~~~~~~~~~
得到商品名称。
得到商品库存。
~~~~~~~物流接口~~~~~~~~
得到物流公司。
  • 在订单实现类,之前的做法是将商品和物流都方法一个订单接口中,通过接口拆分,将订单接口拆分成商品接口和物流接口。

3. 总结

  • 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。

  • 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。(从上面代码可以看出,当调用商品接口方法是,会屏蔽实现类中的物流相关的方法)

  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。(这里提高内聚是指,将该接口不需要的方法提到接口之外,这样接口内都是方法都是该接口所需要的,提高了内聚。)

5. 迪米特法则

1. 个人理解

  • 最少知识原则
  • 只与你的直接朋友交谈,不跟“陌生人”说话
  • 这个设计原则还有点抽象,就是如果不是直接能对话的话,就需要一个中间类来传递双发的信息。

2. 代码示例

老师通过学生联系家长 - 老师和家长就可以用迪米特法则

//老师类
public class MyTeacher {
    private String name;

    public MyTeacher(String name) {
        this.name=name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

父母类
public class MyParent {
    private String name;
    public MyParent(String name){
        this.name=name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//学生类
public class Student {
    private MyParent myParent;
    private MyTeacher myTeacher;

    public MyParent getMyParent() {
        return myParent;
    }
    //叫家长
    public void callParent(){
        System.out.println(myTeacher.getName()+"叫小明通知"+myParent.getName()+"来学校");
    }


    public void setMyParent(MyParent myParent) {
        this.myParent = myParent;
    }

    public MyTeacher getMyTeacher() {
        return myTeacher;
    }

    public void setMyTeacher(MyTeacher myTeacher) {
        this.myTeacher = myTeacher;
    }
}

//测试类

public class LckRuleTest {
    public static void main(String[] args) {
        Student student=new Student();
        student.setMyParent(new MyParent("小明的爸爸"));
        student.setMyTeacher(new MyTeacher("小明的老师"));
        student.callParent();
    }
}

//结果
小明的老师叫小明通知小明的爸爸来学校

3. 总结

  • 使用迪米特原则时要更具具体情况而定,因为该设计原则会产生中间类,如果中间类过多,会导至系统很复杂。

  • 其实迪米特原则可以和spring 的IOC进行类比,ioc相当于中间类,将对象之间的关系交给中间类来管理。目的和ioc容器一样都是为了减低对象之间的耦合关系。

6. 合成复用原则(组合/聚合复用原则)

1. 个人理解

  • 合成复用和继承时对代码实现复用两种策略,但相比之下合成复用更加合理一下。
  • 继承复用,父类和子类的耦合度很高。父类发生变化,子类也会发生变化。继承复用简称“白盒”复用
  • 合成复用依然保持了类的封装性,复合复用简称"黑盒"复用。

2. 代码示例

  1. 用继承来实现
    设计模式学习-6大设计原则_第1张图片

  2. 用复合来实现
    设计模式学习-6大设计原则_第2张图片
    -以上两张图借鉴网站如下:
    借鉴网站

3. 总结

  • 复合复用是我用的很少的设计原则
  • 在好几本书上都建议,少用继承,多用复合(这足以体现复合复用的重要性)

你可能感兴趣的:(设计模式)