设计模式
使用设计模式可以使我们的代码具有更好的可读性、可扩展性、可读性、重用性和高内聚低耦合特性。作为一名程序员,这是我们经常听到的概念,也是我们程序员必须深入学习和理解的知识。
设计模式类型
这和图来源于菜鸟教程
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 这些设计模式提供了一种隐藏创建逻辑的方式,而不是使用它们来创建对象 new 运算符直接实例化对象。这使得程序更灵活地判断需要为给定实例创建哪些对象。 | 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
2 | 这些设计模式关注类别和对象的组合。继承的概念被用来组合接口,并定义组合对象以获得新的功能。 | 适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
3 | 这些设计模式特别关注对象之间的通信。 | 责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
4 | 这些设计模式特别注重表层。这些模式是由 Sun Java Center 鉴定的。 | MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 实体模式的组合(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern) |
以下是设计模式之间关系的总体描述:
七大原则
模块组成元素之间的功能相关性。一类只负责一项责任。
面向对象设计的基本原则之一。子类必须出现在任何基本类别可以出现的地方,衍生物也可以在基本类别的基础上增加新的行为。鸡肋已经设置了标准。虽然不强制子类遵循这些合同,但不能随意修改,这将减少程序的移植,增加程序的耦合(基本类别的修改将影响所有子类别)
这一原则是开闭原则的基础,依赖抽象而不是具体,核心思想面向接口编程。
可以增加接口数量,以确保一个类依赖于另一个类的最小接口,并为每个类建立它们需要的特殊接口。例如,B依赖于A的1、2接口,C依靠A的3、4接口,可以将1、2接口隔离成新的接口。
实体应尽可能少地与其他实体相互作用,使系统功能模块相对独立。
尽量使用合成/聚合,而不是继承。
开放扩展,关闭修改。当程序需要扩展时,不能修改原始代码,以达到热插拔的效果。
单一职责原则
单一职责原则表明模块组成元素之间的功能相关性。一类只负责一项职责。
起初,只有一种类型的动物,食草动物,我们的代码是这样写的。
package com.wangscaler.singleresponsibility; /** * @author wangscaler * @date 2021.06.16 10:25 */ public class SingleResponsibilityPrinciple {
public static void main(String[] args) {
Animal animal = new Animal(); animal.eat("牛"); animal.eat("羊");/span> animal.eat("马"); } } class Animal {
public void eat(String type) {
System.out.println(type + "吃草"); } }
这完全没问题的,随着业务的增长,发现我们增加了食肉类动物,如果我们直接写animal.eat("老虎");
这就违反了我们单一职责的原则,显然是不合适的。那么我们的代码就需要重写。
正确案例一如下:
package com.wangscaler.singleresponsibility;
/** * @author wangscaler * @date 2021.06.16 10:25 */
public class SingleResponsibilityPrinciple {
public static void main(String[] args) {
Carnivorous carnivorous = new Carnivorous();
Herbivorous herbivorous = new Herbivorous();
herbivorous.eat("牛");
herbivorous.eat("羊");
herbivorous.eat("马");
carnivorous.eat("老虎");
carnivorous.eat("狮子");
}
}
class Herbivorous {
public void eat(String type) {
System.out.println(type + "吃草");
}
}
class Carnivorous {
public void eat(String type) {
System.out.println(type + "吃肉");
}
}
这样改,显然是符合单一职责原则的,在类下有很多方法时,可以采取使用。在这里方法较少,这样写的话,开销比较大,所以可以违法类的单一职责原则,让方法保持单一职责原则。
正确案例二:
package com.wangscaler.singleresponsibility;
/** * @author wangscaler * @date 2021.06.16 10:25 */
public class SingleResponsibilityPrinciple2 {
public static void main(String[] args) {
Animals animals = new Animals();
animals.herbivorousEat("牛");
animals.carnivorousEat("老虎");
}
}
class Animals {
public void herbivorousEat(String type) {
System.out.println(type + "吃草");
}
public void carnivorousEat(String type) {
System.out.println(type + "吃肉");
}
}
采用这种写法的话,当我们增加种类,只需要添加新的种类的方法,而之前的代码是不需要动的。比如此时我们增加既吃肉又吃草的动物。只需要这样修改。
package com.wangscaler.singleresponsibility;
/** * @author wangscaler * @date 2021.06.16 10:25 */
public class SingleResponsibilityPrinciple2 {
public static void main(String[] args) {
Animals animals = new Animals();
animals.herbivorousEat("牛");
animals.carnivorousEat("老虎");
//不要计较熊猫吃不吃肉和草,杠精绕路。
animals.eat("熊猫");
}
}
class Animals {
public void herbivorousEat(String type) {
System.out.println(type + "吃草");
}
public void carnivorousEat(String type) {
System.out.println(type + "吃肉");
}
public void eat(String type) {
System.out.println(type + "既吃肉还吃草");
}
}
我们可以看到,现在只需要增加新业务的代码,之前写的代码是没有影响的。
单一职责原则的关键在于将业务中类的不同职责分离划分到不同的类或者接口。原则就是一个方法尽可能的处理一个职责,当然职责间肯定是有关联的,这就需要根据业务和需求来划分隔离了。如果方法较多,最好是在类上隔离,如果方法少且逻辑简单的情况下,可以让类违背单一职责原则,让方法保持该原则。
接口隔离原则
可以增加接口数,来保证一个类依赖于另一个类的最小接口,要为各个类建立它们需要的专用接口,接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加轻便灵活。比如B依赖于A的1、2接口,C依赖于A的3、4接口,就可以把1、2接口隔离出来作为新的接口;3、4隔离出来作为新的接口。
错误示例:
起初我们的接口是写了所有的行为,然而马只依赖于eat、run;鸭子依赖于eat、run、swim;天鹅依赖于所有的方法。对于马来说fly方法显然是没用的方法也得实现,这样将不符合逻辑。
package com.wangscaler.interfacesegregation;
/** * @author wangscaler * @date 2021.06.16 16:35 */
public class InterfaceSegregationPrinciple {
public static void main(String[] args) {
IAction horse = new Horse();
horse.eat();
}
interface IAction {
void eat();
void fly();
void run();
void swim();
}
static class Horse implements IAction {
public void eat() {
System.out.println("马会吃");
}
public void fly() {
}
public void run() {
System.out.println("马会走");
}
public void swim() {
}
}
class Duck implements IAction {
public void eat() {
System.out.println("鸭子会吃");
}
public void fly() {
}
public void run() {
System.out.println("鸭子会走");
}
public void swim() {
System.out.println("鸭子会游泳");
}
}
class swan implements IAction {
public void eat() {
System.out.println("天鹅会吃");
}
public void fly() {
System.out.println("天鹅会飞");
}
public void run() {
System.out.println("天鹅会走");
}
public void swim() {
System.out.println("天鹅会游泳");
}
}
}
我们发现所有的动物均有eat、run的行为,而fly和swim是特有的行为,所以将接口按照接口隔离原则改变后如下:
正确示例。
package com.wangscaler.interfacesegregation;
/** * @author wangscaler * @date 2021.06.16 16:35 */
public class InterfaceSegregationPrinciple1 {
public static void main(String[] args) {
Horse horse = new Horse();
horse.eat();
horse.run();
}
interface IEatAndRunAction {
void eat();
void run();
}
interface IFlyAction {
void fly();
}
interface ISwimAction {
void swim();
}
static class Horse implements IEatAndRunAction {
public void eat() {
System.out.println("马会吃");
}
public void run() {
System.out.println("马会走");
}
}
class Duck implements IEatAndRunAction, ISwimAction {
public void eat() {
System.out.println("鸭子会吃");
}
public void run() {
System.out.println("鸭子会走");
}
public void swim() {
System.out.println("鸭子会游泳");
}
}
class swan implements IEatAndRunAction, ISwimAction, IFlyAction {
public void eat() {
System.out.println("天鹅会吃");
}
public void fly() {
System.out.println("天鹅会飞");
}
public void run() {
System.out.println("天鹅会走");
}
public void swim() {
System.out.println("天鹅会游泳");
}
}
}
**总结:**接口的粒度一定要合理,太小就会导致接口增多,太大会导致灵活性降低,减少代码的冗余,提高系统的内聚性。
本文由 简悦 SimpRead 转码, 原文地址 juejin.cn
在设计模式一中,我们已经讲了两种设计原则,接下来继续讲解。 本文讲解的是依赖倒转原则和里氏替换原则。
在设计模式一我们已经讲了两种设计原则,接下来继续讲解。
依赖倒转原则
依赖于抽象而不依赖于具体,核心思想面向接口编程。目的是制定好规范(设计),而不是实现。
比如我们现在写的代码要自动化部署到服务器,开始我们使用的 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
因为奇异鸟不会飞,所以改写了父类