编程语言
首页 > 编程语言> > Java设计模式之七大原则

Java设计模式之七大原则

作者:互联网

Java设计模式

文章目录

Java设计模式的概述

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。1995 年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」。这 23 种设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解。虽然我命名为“Java设计模式”,但是设计模式并不是 Java 的专利,它同样适用于 C++、C#、JavaScript 等其它面向对象的编程语言。

Java设计模式的目的

编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好:

设计模式七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么这样设计的依据)

设计模式常用的七大原则有:


单一职责原则(Single Responsibility Principle, SRP)

基本介绍

对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2.

示例

非单一职责(错误示范)
public class singleton1 {
    public static void main(String[] args) {
        Vehicle vehicle=new Vehicle();
        vehicle.run("汽车");
        vehicle.run("轮船");
        vehicle.run("飞机");
    }
}

//非单一模式
class Vehicle{
    public void  run(String ve){
        System.out.println(ve+"在公路运行");
    }
}
遵循类单一职责
public class singleton3 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle=new RoadVehicle();
        roadVehicle.run("汽车");
        AirVehicle airVehicle=new AirVehicle();
        airVehicle.run("飞机");
        SeaVehicle seaVehicle=new SeaVehicle();
        seaVehicle.run("轮船");
    }
}
//类单一原则
class RoadVehicle{
    public void run(String ve){
        System.out.println(ve+"在陆地跑");
    }
}

class AirVehicle{
    public void run(String ve){
        System.out.println(ve+"在天空飞");
    }
}

class SeaVehicle{
    public void run(String ve){
        System.out.println(ve+"在海航线");
    }
}

方法单一职责(只有在方法少可用)

public class singleton2 {
    public static void main(String[] args) {
        Vehicle2 vehicle2=new Vehicle2();
        vehicle2.runRoad("汽车");
        vehicle2.runAir("飞机");
        vehicle2.runSea("轮船");
    }
}

//非标准单例模式,在方法少的情况下,可以在方法中实现单例
class Vehicle2{
    public void runRoad(String ve){
        System.out.println(ve+"在公路上跑");
    }

    public void runAir(String ve){
        System.out.println(ve+"在天空上飞行");
    }

    public void  runSea(String ve){
        System.out.println(ve+"在大海上航行");
    }
}

使用原则

优点


接口隔离原则(Interface Segregation Principle, ISP)

基本介绍

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上

示例

非接口隔离原则(错误示范)

类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItR6rclp-1609323889833)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201125141055412.png)]

代码:

可以看出A通过接口使用B只用了三个方法,但是B却要实现五个方法,这样就是不符合接口的隔离原则。

package com.fys.segregation;

public class SegregationTest {

}

interface interface1{
    public void test1();
    public void test2();
    public void test3();
    public void test4();
    public void test5();
}

class B implements  interface1{

    @Override
    public void test1() {
        System.out.println("B实现了test1");
    }

    @Override
    public void test2() {
        System.out.println("B实现了test2");
    }

    @Override
    public void test3() {
        System.out.println("B实现了test3");
    }

    @Override
    public void test4() {
        System.out.println("B实现了test4");
    }

    @Override
    public void test5() {
        System.out.println("B实现了test5");
    }
}

class D implements  interface1{

    @Override
    public void test1() {
        System.out.println("D实现了test1");
    }

    @Override
    public void test2() {
        System.out.println("D实现了test2");
    }

    @Override
    public void test3() {
        System.out.println("D实现了test3");
    }

    @Override
    public void test4() {
        System.out.println("D实现了test4");
    }

    @Override
    public void test5() {
        System.out.println("D实现了test5");
    }
}

class A{
    public void depends1(interface1 i){
        i.test1();
    }
    public void depends2(interface1 i){
        i.test2();
    }
    public void depends3(interface1 i){
        i.test3();
    }
}

class C{
    public void depends1(interface1 i){
        i.test1();
    }
    public void depends2(interface1 i){
        i.test4();
    }
    public void depends3(interface1 i){
        i.test5();
    }
}
遵循接口隔离原则

按隔离原则应当这样处理: 将接口Interface1拆分为独立的几个接口, 类A和类C分别与他们需要的接口建立依赖 关系。也就是采用接口隔离原则

类图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Vx638Iu-1609323889848)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201125143024695.png)]

代码

这样把一个大接口,分到最小接口,就可以满足接口隔离原则

package com.fys.segregation;

public class SegregationTest2{
    public static void main(String[] args) {
        AA aa=new AA();
        aa.depends1(new BB());
        aa.depends2(new BB());
        aa.depends3(new BB());

        CC cc=new CC();
        cc.depends1(new DD());
        cc.depends2(new DD());
        cc.depends3(new DD());
    }
}

interface interface2{
    public void test1();
}
interface interface3{
    public void test2();
    public void test3();
}
interface interface4{
    public void test4();
    public void test5();
}

class BB implements  interface2,interface3{

    @Override
    public void test1() {
        System.out.println("B实现了test1");
    }

    @Override
    public void test2() {
        System.out.println("B实现了test2");
    }

    @Override
    public void test3() {
        System.out.println("B实现了test3");
    }
}

class DD implements  interface2,interface4{

    @Override
    public void test1() {
        System.out.println("D实现了test1");
    }

    @Override
    public void test4() {
        System.out.println("D实现了test4");
    }

    @Override
    public void test5() {
        System.out.println("D实现了test5");
    }
}

class AA{
    public void depends1(interface2 i){
        i.test1();
    }
    public void depends2(interface3 i){
        i.test2();
    }
    public void depends3(interface3 i){
        i.test3();
    }
}

class CC{
    public void depends1(interface2 i){
        i.test1();
    }
    public void depends2(interface4 i){
        itest4();
    }
    public void depends3(interface4 i){
        i.test5();
    }
}

使用原则

优点

符合我们常说的高内聚,低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性


依赖倒转原则(Dependence Inversion Principle, DIP)

基本介绍

依赖倒转原则(Dependence Inversion Principle)是指:

通俗易懂的话就是:

示例

非依赖倒转(错误示范)

在这种情况下,person和email之间发生了依赖关系,而且person想增加其它发送方式,又要新建其他方法非常不方便。

package com.fys.inversion;

public class DependencyInversion {
    public static void main(String[] args) {
        Person person=new Person();
        person.sendEmail(new Email());
    }
}

class Email{
    public void send(){
        System.out.println("发送邮件");
    }
}

class Person{
    public void sendEmail(Email email){
       email.send();
    }
}
遵循依赖倒转

让功能类实现接口,person依赖接口,一样也可以完成需求,而且扩展性很高,满足了实现类只依赖接口。

package com.fys.inversion.improve;

public class DependencyInversion {
    public static void main(String[] args) {
        Person person=new Person();
        person.receive(new Email());
        person.receive(new Qq());
    }
}
interface Send{
    public void getInfo();
}


class Email implements Send{

    @Override
    public void getInfo() {
        System.out.println("发送邮件");
    }
}

class Qq implements Send{

    @Override
    public void getInfo() {
        System.out.println("发送qq消息");
    }
}

class Person{
    public void receive(Send send){
        send.getInfo();
    }
}

依赖倒转的三种方式

接口传递
package com.fys.inversion.three;

public class DependencyPass {
    public static void main(String[] args) {
        OpenAndClose openAndClose=new OpenAndClose();
        openAndClose.open(new ChangHong());
    }
}
//打开与关闭的抽象
interface IOpenAndClose{
    public void open(ITV itv);
}
//电视机的抽象
interface ITV{
    public void play();
}
//电视机的实现
class ChangHong implements ITV{

    @Override
    public void play() {
        System.out.println("长虹电视打开");
    }
}
//打开与关闭的实现
class OpenAndClose implements IOpenAndClose{
    @Override
    public void open(ITV itv) {
        itv.play();
    }
}
构造方法传递
package com.fys.inversion.three;

public class DependencyPass {
    public static void main(String[] args) {
        OpenAndClose openAndClose=new OpenAndClose(new ChangHong());
        openAndClose.open();
    }
}
//打开与关闭的抽象
interface IOpenAndClose{
    public void open();
}
//电视机的抽象
interface ITV{
    public void play();
}
//电视机的实现
class ChangHong implements ITV{

    @Override
    public void play() {
        System.out.println("长虹电视打开");
    }
}
//打开与关闭的实现
class OpenAndClose implements IOpenAndClose{

    private ITV itv;

    public OpenAndClose(ITV itv){
        this.itv=itv;
    }

    @Override
    public void open() {
        this.itv.play();
    }
}
setter方式传递
package com.fys.inversion.three;

public class DependencyPass {
    public static void main(String[] args) {
        OpenAndClose openAndClose=new OpenAndClose();
        openAndClose.setTv(new ChangHong());
        openAndClose.open();
    }
}
//打开与关闭的抽象
interface IOpenAndClose{
    public void open();
    public void setTv(ITV itv);
}
//电视机的抽象
interface ITV{
    public void play();


}
//电视机的实现
class ChangHong implements ITV{

    @Override
    public void play() {
        System.out.println("长虹电视打开");
    }
}
//打开与关闭的实现
class OpenAndClose implements IOpenAndClose{

    private ITV itv;

    @Override
    public void open() {
        this.itv.play();
    }

    @Override
    public void setTv(ITV itv) {
        this.itv=itv;
    }
}

好处

使用原则


里氏替换原则(Dependence Inversion Principle, DIP)

了解继承

继承是面向对象三大特性之一,是一种非常优秀的语言机制,它有如下有点:

继承有它的优点,但是也有一些致命的缺点:

基本介绍

示例

非里氏原则(错误示范)

这个程序,B继承了A,却把A的方法重写了,如果B没有注意,还以为调用的是A的方法,结果就会出错了。

public class LisKov {
    public static void main(String[] args) {
      A a = new A(); 
     System.out.println("11-3=" + a.func1(11, 3)); 	
     System.out.println("1-8=" + a.func1(1, 8));
	 System.out.println("-----------------------"); 
      B b = new B(); 
     System.out.println("11-3=" + b.func1(11, 3)); 
     System.out.println("1-8=" + b.func1(1, 8)); 
     System.out.println("11+3+9=" + b.func2(11, 3));   
    }
}

class A {
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}
class B extends A {
    public int func1(int a, int b) {
        return a + b;
    }
    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }
}
遵循里氏原则

对于上面的程序,我们该怎么优化呢?

通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉, 采用依赖,聚合,组合等关系代替.

public class LisKov {
    public static void main(String[] args) {
        B b=new B();
        b.func3(1,1);
    }
}

class C{

}

class A extends C {
    public int func1(int a, int b) {
        return a - b;
    }
}
class B extends C {
    //这里是用组合的方式使用A
    private A a=new A();

    public int func1(int a, int b) {
        return a + b;
    }
    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }
    public int func3(int a, int b){
        return this.a.func1(a,b);
    }
}

加深理解

多态与LSP是否矛盾

在学习Java里面的多态时,我们知道多态的前提就是要有子类继承父类并且子类重写父类的方法。那这是否和LSP矛盾呢?因为LSP要求我们只可以扩展父类的功能,但不能改变父类原有的功能,也就是不能对父类原有的方法进行重写,只能去实现父类的方法或重载。下面是我在知乎上找到的一种比较合理的解释:

不符合LSP最常见的情况就是:父类和子类都是非抽象类,且父类的方法被子类重新定义,这样实现继承会造成子类和父类之间的强耦合,将不相关的属性和方法搅和在一起,不利于程序的维护和扩展。所以总结一句:尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承(也就是面向接口和抽象编程)


迪米特法则((Law of Demeter, LoD))

基本介绍

示例

非迪米特法则(错误示范)

ming和mark互相不认识,那为什么代表ming类中会有代表mark类呢?这样明显是违背了迪米特法则的。现在我们对上面的代码进行重构,根据迪米特法则的第二点:从依赖者的角度来看,只依赖应该依赖的对象。在本例中,张三只认识李四,那么只能依赖李四

public class Demeter {
    public static void main(String[] args) {
        Ming ming=new Ming();
        ming.work();
    }
}
//ming需要找朋友jack完成工作,jack不会去求助自己的朋友mark,mark完成了
class Ming{

    public Jack getFriend(){//方法返回值,jack是直接朋友
        return new Jack();
    }

    public void work(){
       Jack jack=getFriend();
       Mark mark=jack.getFriend();//局部变量,mark是间接朋友
       mark.work();
    }
}

class Jack{
    public Mark getFriend(){
        return new Mark();
    }
}

class Mark{
    public void work(){
        System.out.println("做完");
    }
}
遵循迪米特法则
public class Demeter {
    public static void main(String[] args) {
        Ming ming=new Ming();
        ming.work();
    }
}
//ming需要找朋友jack完成工作,jack不会去求助自己的朋友mark,mark完成了
class Ming{

    public Jack getFriend(){//方法返回值,jack是直接朋友
        return new Jack();
    }

    public void work(){
       Jack jack=getFriend();
       jack.work();
    }
}

class Jack{
    public Mark getFriend(){
        return new Mark();
    }
    public void work(){
        Mark mark=getFriend();//局部变量,mark是间接朋友
        mark.work();
    }
}

class Mark{
    public void work(){
        System.out.println("做完");
    }
}

注意事项


合成复用原则(Composite Reuse Principle)

基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承


开闭原则(Open Close Principle, OCP)

基本介绍

示例

非开闭原则(错误示范)

可以看到GraphicEditor是使用方,但是现在新增三角形会修改使用方,不满足开闭原则。

package com.fys.ocp;

public class Ocp {

   public static void main(String[] args) {
      GraphicEditor graphicEditor = new GraphicEditor();
      graphicEditor.drawShape(new Rectangle());
      graphicEditor.drawShape(new Circle());
      graphicEditor.drawShape(new Triangle());
   }

}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
   //接收Shape对象,然后根据type,来绘制不同的图形
   public void drawShape(Shape s) {
      if (s.m_type == 1)
         drawRectangle(s);
      else if (s.m_type == 2)
         drawCircle(s);
      else if (s.m_type == 3)
         drawTriangle(s);
   }

   //绘制矩形
   public void drawRectangle(Shape r) {
      System.out.println(" 绘制矩形 ");
   }

   //绘制圆形
   public void drawCircle(Shape r) {
      System.out.println("绘制圆形");
   }

   //绘制三角形
   public void drawTriangle(Shape r) {
      System.out.println("绘制三角形");
   }
}

//Shape类,基类
class Shape {
   int m_type;
}

class Rectangle extends Shape {
   Rectangle() {
      super.m_type = 1;
   }
}

class Circle extends Shape {
   Circle() {
      super.m_type = 2;
   }
}

//新增画三角形
class Triangle extends Shape {
   Triangle() {
      super.m_type = 3;
   }
}

遵循开闭原则

修改过后,需要新增图形,只需要继承基类,然后实现方法就可以啦

package com.fys.ocp;

public class Ocp {

   public static void main(String[] args) {
      GraphicEditor graphicEditor = new GraphicEditor();
      graphicEditor.drawShape(new Rectangle());
      graphicEditor.drawShape(new Circle());
      graphicEditor.drawShape(new Triangle());
   }

}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
   //接收Shape对象,然后根据type,来绘制不同的图形
   public void drawShape(Shape s) {
      s.draw();
   }
}

//Shape类,基类
abstract class Shape {
   int m_type;
   public abstract void draw();
}

class Rectangle extends Shape {
   Rectangle() {
      super.m_type = 1;
   }

   @Override
   public void draw() {
      System.out.println("画矩形");
   }
}

class Circle extends Shape {
   Circle() {
      super.m_type = 2;
   }

   @Override
   public void draw() {
      System.out.println("画圆形");
   }
}

//新增画三角形
class Triangle extends Shape {
   Triangle() {
      super.m_type = 3;
   }

   @Override
   public void draw() {
      System.out.println("画三角形");
   }
}

注意:

把开闭原则应用于实际项目中,我们需要注意至关重要的一点:抽象约束
 抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:


总结每个原则的核心

原则缩写核心思想
单一职责SRP类功能越少越好。一个类所拥有的方法尽可能为最少,便于维护,而且在用这个类的时候,如果该类的方法过多的话,在使用这个类的时候就会不合适,而且会出现不必要的麻烦。
接口隔离ISP类似SRP(单一职责),功能越少越好。宁肯多写一个接口,也不用在一个接口里写太多的方法。
依赖倒置原则DIP高端类尽量依赖于接口,而不依赖于低端子类。就是电脑主板依赖于接口,而内存条、硬盘等去实现接口。如果主板直接依赖于内存条、硬盘的话,内存条或者硬盘出现问题,整个主板就都用不成了。
里氏替换原则LSP表示子类替换父类,父类指向子类引用对象。声明一个父类,new上一个子类,来调用父类的方法,替换父类。
迪米特法则LOD类与类之间的关联关系越少越好。如果类与类之间的关联关系多的话,每修改一个类就要去修改与其关联的所有的类,如果有一个“调度中心”的话,任何一个类出现问题,不管是修改还是删除,都是“调度中心”的事儿了,就不用管其他的类了。
复用原则CARP原则是尽量使用合成/聚合的方式,而不是使用继承
开放封闭原则OCP对外扩展开放,对内修改封闭。

总结设计原则核心思想

参考文章

原则缩写核心思想
单一职责SRP类功能越少越好。一个类所拥有的方法尽可能为最少,便于维护,而且在用这个类的时候,如果该类的方法过多的话,在使用这个类的时候就会不合适,而且会出现不必要的麻烦。
接口隔离ISP类似SRP(单一职责),功能越少越好。宁肯多写一个接口,也不用在一个接口里写太多的方法。
依赖倒置原则DIP高端类尽量依赖于接口,而不依赖于低端子类。就是电脑主板依赖于接口,而内存条、硬盘等去实现接口。如果主板直接依赖于内存条、硬盘的话,内存条或者硬盘出现问题,整个主板就都用不成了。
里氏替换原则LSP表示子类替换父类,父类指向子类引用对象。声明一个父类,new上一个子类,来调用父类的方法,替换父类。
迪米特法则LOD类与类之间的关联关系越少越好。如果类与类之间的关联关系多的话,每修改一个类就要去修改与其关联的所有的类,如果有一个“调度中心”的话,任何一个类出现问题,不管是修改还是删除,都是“调度中心”的事儿了,就不用管其他的类了。
复用原则CARP原则是尽量使用合成/聚合的方式,而不是使用继承
开放封闭原则OCP对外扩展开放,对内修改封闭。

总结设计原则核心思想

参考文章

标签:之七大,父类,Java,原则,void,class,new,设计模式,public
来源: https://blog.csdn.net/ASDASDADF/article/details/111996144