设计模式二

178

这是我参与更文挑战的第14天,活动详情查看: 更文挑战

设计模式

设计模式一我们已经讲了两种设计原则,接下来继续讲解。

依赖倒转原则

依赖于抽象而不依赖于具体,核心思想面向接口编程。目的是制定好规范(设计),而不是实现。

比如我们现在写的代码要自动化部署到服务器,开始我们使用的github进行的自动化部署。实现过程就是程序员-->提交代码到github-->自动化部署。

程序员-->提交代码

github-->自动化部署

错误示例

package com.wangscaler.dependenceinversion;

/**
 * @author wangscaler
 * @date 2021.06.16 17:43
 */
public class DependenceInversionPrinciple {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        programmer.commit(new Github());
    }

    static class Github {
        public String cicd() {
            return "github 自动化部署完成";
        }
    }

    static class Programmer {
        public void commit(Github github) {
            System.out.println(github.cicd());
        }
    }
}

有一天,github仓库访问太慢了,我们不想用了,换成gitlab,这时候我们新建一个Gitlab仓库并加上的cicd方法,但是我们虽然有了这个仓库,却没法自动化部署,因为我们的程序员只知道github。此时Programmer依赖了Github,这样是不合理的,模块与模块之间耦合度太高,生产力太低。

正确示例

package com.wangscaler.dependenceinversion;

/**
 * @author wangscaler
 * @date 2021.06.16 17:43
 */
public class DependenceInversionPrinciple1 {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        programmer.commit(new Gitlab());
    }

    static class Github implements IWarehouse {
        public String cicd() {
            return "github 自动化部署完成";
        }
    }

    static class Gitlab implements IWarehouse {
        public String cicd() {
            return "gitlab 自动化部署完成";
        }
    }

    public interface IWarehouse {
        public String cicd();
    }

    static class Programmer {
        public void commit(IWarehouse warehouse) {
            System.out.println(warehouse.cicd());
        }
    }
}

因为github和gitlab都属于仓库,而且都是有cicd的方法,所以定义仓库接口,让他们实现这个接口就行了。如果再后来,又想换成华为云仓库,只需要增加华为云这个类就行了。

package com.wangscaler.dependenceinversion;

/**
 * @author wangscaler
 * @date 2021.06.16 17:43
 */
public class DependenceInversionPrinciple1 {
    public static void main(String[] args) {
        Programmer programmer = new Programmer();
        programmer.commit(new Huawei());
    }

    static class Github implements IWarehouse {
        public String cicd() {
            return "github 自动化部署完成";
        }
    }

    static class Gitlab implements IWarehouse {
        public String cicd() {
            return "gitlab 自动化部署完成";
        }
    }

    static class Huawei implements IWarehouse {
        public String cicd() {
            return "华为云仓库自动化部署完成";
        }
    }

    public interface IWarehouse {
        public String cicd();
    }

    static class Programmer {
        public void commit(IWarehouse warehouse) {
            System.out.println(warehouse.cicd());
        }
    }
}

总结: 根据依赖倒转原则,我们关注的抽象而不是具体。在这个例子当中,我们的设计思路应该是程序员-->提交代码到仓库-->自动化部署。无论是github、gitlab还是华为云,他们抽象出来都是仓库即从下层模块github开始,想想看他能抽象化出什么。这个抽象类就相当于一个缓冲层,增强代码的可扩展性。

里氏替换原则

所有引用基类的地方都能透明的使用他的子类。面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现,而派生类也能够在基类的基础上增加新的行为。会降低程序的移植性,增加程序的耦合性(基类的修改会影响到所有子类)。子类继承父类时,尽量不要重写父类的方法。

比如我们有两个鸟,燕子和奇异鸟(不会飞),本来我们常识鸟都是飞的,所以代码这样写的

package com.wangscaler.liskovsubstitution;
/**
 * @author wangscaler
 * @date 2021.06.17
 */
public class LiskovSubstitutionPrinciple {
    public static void main(String[] args) {
        Swallow swallow = new Swallow();
        Kiwi kiwi = new Kiwi();
        swallow.setSpeed(110);
        kiwi.setSpeed(120);
        System.out.println(swallow.getFlyTime(240));
        System.out.println(kiwi.getFlyTime(240));
    }

    static class Bird {
        double speed;

        public void setSpeed(double speed) {
            this.speed = speed;
        }

        public double getFlyTime(double distance) {
            return (distance / speed);
        }
    }

    static class Swallow extends Bird {
    }

    static class Kiwi extends Bird {
        @Override
        public void setSpeed(double speed) {
            speed = 0;
        }
    }
}

执行结果

2.1818181818181817
Infinity

因为奇异鸟不会飞,所以改写了父类的方法,导致我们在父类设置速度,理所当然认为在奇异鸟中设置速度也是这个,最终导致错误的产生,我们应该设置更基础的父类,来避免字类继承的时候重写父类的方法。

package com.wangscaler.liskovsubstitution;

/**
 * @author wangscaler
 * @date 2021.06.17
 */
public class LiskovSubstitutionPrinciple {
    public static void main(String[] args) {
        Swallow swallow = new Swallow();
        Kiwi kiwi = new Kiwi();
        swallow.setFlySpeed(110);
        kiwi.setSpeed(120);
        System.out.println(swallow.getFlyTime(240));
        System.out.println(kiwi.getTime(240));
    }

    static class Animal {
        double speed;

        public void setSpeed(double speed) {
            this.speed = speed;
        }

        public double getTime(double distance) {
            return (distance / speed);
        }
    }

    static class Bird extends Animal {
        double flyspeed;

        public void setFlySpeed(double speed) {
            this.flyspeed = speed;
        }

        public double getFlyTime(double distance) {
            return (distance / flyspeed);
        }
    }

    static class Swallow extends Bird {
    }

    static class Kiwi extends Animal {

    }
}

总结: 里氏替换原则就是要求我们集成基类,尽量不要重写父类,可以增加功能。如果必须要重写父类方法,一定要符合输入条件比父类更加宽松,输出条件比父类更加严格的要求。比如一开始的代码,父类Bird的输入条件是范围,而子类Kiwi的输入条件变成了0显然是不符合规则的。