Java日记之设计模式初探

2019-10-25  本文已影响0人  居居居居居居x

前言

设计模式是在开发中必须要掌握的一种技能,包括各个项目的结构设计以及一些框架源码的解读,里面都包含有设计模式,设计模式也是面试中经常问到,从这篇文章开始记录全部23种设计模式的解析,23种设计模式根据准则来分类总共分为三大类,创建型、结构形以及行为型。此篇文章的大部分源代码出自于Java设计模式精讲 Debug方式+内存分析,并结合我自己的理解,已获得课程老师同意。


感谢geely老师

设计模式的七大原则

在讲设计模式之前,首先就要讲设计模式的七大原则:

  1. 开闭原则(open closed principle)
  2. 依赖倒置原则(dependence inversion principle)
  3. 单一职责原则(Single Responsibility Principle)
  4. 接口隔离原则(interface segregation principle)
  5. 迪米特原则(law of demeter LOD)
  6. 里氏替换原则(LSP liskov substitution principle)
  7. 合成复用原则(Composite Reuse Principle)

1. 开闭原则(open closed principle)

代码举例:

public interface ICourse {
    Integer getId();
    String getName();
    Double getPrice();
}

//实现类
public class JavaCourse implements ICourse{
    private Integer Id;
    private String name;
    private Double price;

    public JavaCourse(Integer id, String name, Double price) {
        this.Id = id;
        this.name = name;
        this.price = price;
    }

    public Integer getId() {
        return this.Id;
    }

    public String getName() {
        return this.name;
    }

    public Double getPrice() {
        return this.price;
    }

}

首先创建一个接口和它的实现类,这时候假如说要对JavaCourse这个类进行拓展,但是不修改原来的代码,该怎么做呢?很简单,继承这个类,并进行拓展就行,以下的例子:

//拓展类
public class JavaDiscountCourse extends JavaCourse {

    public JavaDiscountCourse(Integer id, String name, Double price) {
        super(id, name, price);
    }

    public Double getDiscountPrice(){
        return super.getPrice()*0.8;
    }

}

//测试类
public class Test {
    public static void main(String[] args) {
        ICourse iCourse = new JavaDiscountCourse(96, "Java从零到企业级电商开发", 348d);
        JavaDiscountCourse javaCourse = (JavaDiscountCourse) iCourse;
        System.out.println("课程ID:" + javaCourse.getId() + " 课程名称:" + javaCourse.getName() 
        + " 课程原价:" + javaCourse.getPrice() + " 课程折后价格:" + javaCourse.getDiscountPrice() + "元");

    }
}
开闭原则代码运行结果

从代码就可以看出来了,这就很符合开闭原则,我们只对类进行拓展和修改,如果是修改方法的话,通过接口去进行抽象出来,然后去实现就可以,这就是面向接口编程。

2.依赖倒置原则(dependence inversion principle)

代码举例:

定义接口
public interface ICourse {
    void studyCourse();
}

实现类
public class JavaCourse implements ICourse {

    @Override
    public void studyCourse() {
        System.out.println("学习Java课程");
    }
}

public class PythonCourse implements ICourse {

    @Override
    public void studyCourse() {
        System.out.println("学习Python课程");
    }
}

首先定义接口和它的实现类,接着我们面向接口编程呢,我只需要要创建一个传递接口实现的类就好了。

public class Ju {

    public void setiCourse(ICourse iCourse) {
        this.iCourse = iCourse;
    }

    private ICourse iCourse;



    public void studyImoocCourse(){
        iCourse.studyCourse();
    }

    
}

//测试类实现
public class Test {

    public static void main(String[] args) {
        Ju ju = new Ju();
        ju.setiCourse(new JavaCourse());
        ju.studyImoocCourse();

        ju.setiCourse(new FECourse());
        ju.studyImoocCourse();

    }
}
依赖倒置原则代码运行结果
从代码就可以看出来,这就是面向接口编程,你需要实现接口什么样的方法,就只要把实现这个接口的类实例化就可以轻松的进行调用,这样就降低了耦合度,也就不用如果需要替换类,就需要从核心逻辑上替换实现的类了,也提高了代码的可维护性。

3.单一职责原则(Single Responsibility Principle)

private void updateUserInfo(String userName,String address,boolean bool){
    if(bool){
        //todo something1
    }else{
        //todo something2
    }
}

比如说一个有新的业务需求(如上代码),你可能就需要在代码上面直接更改,这样子如此长期改下去可能会发生问题。我们就推荐各创建一个类并负责各自的职责。

//各自类的实现
public class FlyBird {
    public void mainMoveMode(String birdName){
        System.out.println(birdName+"用翅膀飞");
    }
}

public class WalkBird {
    public void mainMoveMode(String birdName){
        System.out.println(birdName+"用脚走");
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        FlyBird flyBird = new FlyBird();
        flyBird.mainMoveMode("大雁");

        WalkBird walkBird = new WalkBird();
        walkBird.mainMoveMode("鸵鸟");

    }
}

一些公共的方法可以使用抽象类来进行实现,不同的业务,就创建各自的类来进行负责其职责就好。

4.接口隔离原则(interface segregation principle)

代码举例:

//接口定义
public interface IAnimalAction {
    void eat();
    void fly();
    void swim();

}

public interface IEatAnimalAction {
    void eat();
}

public interface IFlyAnimalAction {
    void fly();
}

public interface ISwimAnimalAction {
    void swim();
}

//实现类
public class Bird implements IAnimalAction {
    @Override
    public void eat() {

    }

    @Override
    public void fly() {

    }

    @Override
    public void swim() {

    }
}

public class Dog implements ISwimAnimalAction,IEatAnimalAction {

    @Override
    public void eat() {

    }

    @Override
    public void swim() {

    }
}

这就是一个接口隔离原则的设计了,因为比较简单所有没什么好说的,就是通过一个接口来对应一个类以及使用多继承。

5.迪米特原则(law of demeter LOD)

代码举例:
比如Boss说有一个需求就是查询现在有多少门线上课程,这就关系了三个类,TeamLeader、Course和Boss。

public class Boss {

    public void commandCheckNumber(TeamLeader teamLeader){
        teamLeader.checkNumberOfCourses();
    }

}

public class Course {
}

public class TeamLeader {
    public void checkNumberOfCourses(){
        List<Course> courseList = new ArrayList<Course>();
        //模拟查询课程逻辑
        for(int i = 0 ;i < 20;i++){
            courseList.add(new Course());
        }
        System.out.println("在线课程的数量是:"+courseList.size());
    }

}

//测试类
public class Test {
    public static void main(String[] args) {
        Boss boss = new Boss();
        TeamLeader teamLeader = new TeamLeader();
        boss.commandCheckNumber(teamLeader);

    }
}
迪米特原则代码运行结果

从这段代码我们看到Course是有TeamLeader来生成的,Course是不和Boss发生接触的,Boss也不需要知道Course,这就是迪米特原则,Boss不需要关心那么多细节,只要知道结果就行。

6. 里氏替换原则(LSP liskov substitution principle)

代码举例:
举个例子说明下继承的风险(以下例子来源其他博客,侵删)

public class Father {
    public int subtraction(int a, int b) {
        return a - b;
    }

}

public class Test {

    public static void main(String[] args) {
        Father father = new Father();
        System.out.println("100-50=" + father.subtraction(100, 50));
        System.out.println("100-80=" + father.subtraction(100, 80));
    }
}
里氏替换原则代码运行结果1

上面就是一个很简单减法运算,现在我们需要增加一个新的功能,完成两数相加,然后再与100求和,由类Sun来负责。

public class Sun extends Father {

    @Override
    public int subtraction(int a, int b) {
        return a + b;
    }

    public int addition(int a, int b) {
        //重写后subtraction(a,b)改为a+b
        return subtraction(a, b) + 100;
    }

}

public class Test {

    public static void main(String[] args) {
        Sun sun = new Sun();
        System.out.println("100-50="+sun.subtraction(100, 50));
        System.out.println("100-80="+sun.subtraction(100, 80));
        System.out.println("100+20+100="+sun.addition(100, 20));
    }
}
里氏替换原则代码运行结果2

我们发现原本运行正常的相减功能发生了错误。原因就是类Sun在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类Sun重写后的方法,造成原本运行正常的功能出现了错误。在本例中,引用基类Father完成的功能,换成子类Sun之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

7. 合成复用原则(Composite Reuse Principle)

代码举例:

//定义接口
public abstract class DBConnection {

    public abstract String getConnection();
}

//实现接口类
public class MySQLConnection extends DBConnection {
    @Override
    public String getConnection() {
        return "MySQL数据库连接";
    }
}

public class PostgreSQLConnection extends DBConnection {
    @Override
    public String getConnection() {
        return "PostgreSQL数据库连接";
    }
}
public class ProductDao{

    private DBConnection dbConnection;

    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void addProduct(){
        String conn = dbConnection.getConnection();
        System.out.println("使用"+conn+"增加产品");
    }
}

public class Test {
    public static void main(String[] args) {
        ProductDao productDao = new ProductDao();
        productDao.setDbConnection(new PostgreSQLConnection());
        productDao.addProduct();
    }
}

从代码就可以看出,通过ProductDao类注入你需要的对象,就能实现不同的业务逻辑,通过setDbConnection()就可以实现对象的组合和复用,当然也可以通过构造方法来进行,而以后我们如果要增加产品的话,只需要写一个和MySQLConnection类一样平级的类,继承DBConnection接口,具体的选择就可以额交给客户端就好了。

参考

上一篇 下一篇

猜你喜欢

热点阅读