网站首页 > 精选教程 正文
卡车是一种多功能的设备。他们可以根据车厢挂载的零件类型来执行不同的任务,如果载重允许的话,甚至可以装载多个车厢。
简而言之,卡车可以通过不同的零件以进行扩展,但不能改装每个零件的内部单元(例如发动机或机舱)。可拓展的代码也应该像卡车挂载零件一样得心应手。
内容涉及到:Java相关问题Java学习资料+电子书籍+学习视频,免费打包分享,私信【Java】即可获取。
制冰机问题
假设我们必须要编写一个程序来为 Ted&Kelly 公司制作可可冰激凌。
public class IceCreamMachine
{
public void MakeIceCream()
{
Console.WriteLine("Cacao ice-cream"); //logic to create cacao ice cream
}
}
我们将程序交付给客户,虽然这段代码很简单,但是看上去没有什么问题,而且没有违反任何原则。客户目前是非常满意的。
但是事情并不是到这里就结束了,客户总是会有一些新的需求。这就意味着我们需要不断调整我们的代码。
添加新的口味
没想到程序刚上线就如此火爆,于是Ted&Kelly 希望扩大规模。他们要求我们还要能够生产香草味的冰激凌。
短暂思考之后,我们给出了以下解决方案:
public class IceCreamMachine
{
public void MakeIceCream(string flavor)
{
if(flavor == "cacao")
{
Console.WriteLine("Cacao ice-cream"); //logic to create cacao ice-cream
}
else if (flavor == "vanilla")
{
Console.WriteLine("Vanilla ice-cream"); //logic to create vanilla ice-cream
}
else
{
throw new Exception("Flavor not supported");
}
}
}
我们修改了最初的代码,添加了一个参数以指定所需的口味,并添加了 if 语句以在逻辑之间切换。由于我们已经改变了最初的函数签名,因此调用我们代码的方法将被破坏,但是至少从现在开始,我们应该在不破坏更改的情况下支持其他口味的生产。
创建可可和香草组合
业务进展顺利,但是很快客户就提出了第二个需求,希望我们能够生产由可可和香草制成的冰淇淋。事情开始变得有点复杂,但是我们仍然能够处理。
public class IceCreamMachine
{
public void MakeIceCream(string flavor)
{
if(flavor == "cacao")
{
Console.WriteLine("Cacao ice-cream"); //logic to create cacao ice-cream
}
else if (flavor == "vanilla")
{
Console.WriteLine("Vanilla ice-cream"); //logic to create vanilla ice-cream
}
else if (flavor == "cacao-vanilla")
{
//copy & paste the cacao ice-cream logic
Console.WriteLine("Cacao ice-cream");
//copy & paste the vanilla ice-cream logic
Console.WriteLine("Vanilla ice-cream");
}
else
{
throw new Exception("Flavor not supported");
}
}
}
我们又添加了一个if语句,在这种情况下,生产冰淇淋的逻辑被复制到每个 if 内部。在实际的应用程序中,我可能会在单独的服务中提取生产的逻辑。但是,正如我们将看到的那样,提取服务并不总是最好的方案。
当 Ted&Kelly 要求支持生产更多口味时,会发生什么。如果他想进一步将它们结合起来怎么办?仅添加 if 子句并不是理想的解决方案。
这种解决方案产生的问题
每次我们添加新的口味或组合时,我们都必须更新 IceCreamMachine 类,这就意味着:
- 我们更新已经部署的代码
- 这个类变得越来越复杂,易读性变得越来越差。假设我们有 100 种口味,这个类很容易膨胀到 5000+ 行代码
- 我们可能会破坏现有的单元测试
现在回想一下开头卡车的类比,当你每次更换车厢挂载的零件时,你会每次都更换引擎吗?显然不会,看我们如何解决这个问题。
传统的可拓展性方法
Bertrand Meyer 是最早提出开闭原则这一术语的人,他定义 “软件实体(类,模块,函数,等) 应当对扩展开放,对修改关闭。”
换句话说,每当我们需要向旧对象添加新行为时,都可以根据需要继承和更新它们。开闭原则是那些易于理解却难以应用的原则之一。
让我们遵循这种方法重写刚刚的代码:
public abstract class BaseIceCream
{
public abstract void MakeIceCream();
}
public class CacaoIceCream : BaseIceCream
{
public override void MakeIceCream()
{
Console.WriteLine("Cacao ice-cream");
}
}
public class VanillaIceCream : BaseIceCream
{
public override void MakeIceCream()
{
Console.WriteLine("Vanilla ice-cream");
}
}
public class CacaoAndVanilla : CacaoIceCream
{
public override void MakeIceCream()
{
base.MakeIceCream();
Console.WriteLine("Vanilla ice-cream"); //duplicate vanilla logic because we can't iherit both cacao and vanilla
}
}
原始类分为了四个类,每个类代表一种生产口味。通过此解决方案,我们解决了所有最初的问题:
- 每个类都短小精悍,可维护性和易读性都很高
- 当需要添加新口味时,我们只需要添加一个新的类
- 不会影响现有的单元测试
代码看起来已经没有任何问题了,我们完全可以到这里就停止思考,但是其实这种解决方案还是存在一些问题的:
- 不能继承多个类,因此对于两种口味的结合,我们必须复制一些代码或将逻辑提取到服务中
- 如果基类中的代码被更新,所有子类都会受到影响。假设基类通过构造函数注入了一些依赖关系,每当我们添加新的依赖关系时,所有子代都必须将该参数解析为基本构造函数
现在可拓展性的方法
当 Robert C. Martin 重申 Meyer 的开闭原则时,他做了一些更新。偏爱继承而不是继承。
组合对象时,我们可以自由地随意组合任意数量和所需的组合。而且,如果我们针对抽象类(接口)进行编程,则可以修改已有代码的行为而无需进行修改代码。让我们看看最终的解决方案:
public interface IMakeIceCream
{
void Make();
}
public class CacaoIceCream : IMakeIceCream
{
private IMakeIceCream iceCreamMaker;
public CacaoIceCream(IMakeIceCream iceCreamMaker = null) //optional argument, another flavor isn't required
{
this.iceCreamMaker = iceCreamMaker;
}
public void Make()
{
if(iceCreamMaker != null) //if flavor passed in, make that first
{
iceCreamMaker.Make();
}
Console.WriteLine("Cacao ice-cream");
}
}
public class VanillaIceCream : IMakeIceCream
{
private IMakeIceCream iceCreamMaker;
public VanillaIceCream(IMakeIceCream iceCreamMaker = null) //optional argument, another flavor isn't required
{
this.iceCreamMaker = iceCreamMaker;
}
public void Make()
{
if (iceCreamMaker != null) //if flavor passed in, make that first
{
iceCreamMaker.Make();
}
Console.WriteLine("Vanilla ice-cream");
}
}
现在将原始的类分成了三个对象:
- IMakeIceCream 接口定义了制作冰淇淋的通用抽象
- CacaoIceCream 实现了 IMakeIceCream
- VanillaIceCream 实现了 IMakeIceCream
通过使用接口,我们将类和实现解耦。接口是封闭不可修改的,因此一旦我们定义了对象,就无法更改。但是我们可以为新的功能定义新接口并继承它们。这使得代码可扩展。
为什么要向每个构造函数添加“ IMakeIceCream”参数?新代码是否具有旧方法或旧方法的所有功能?
答案是肯定的。可可香草组合仍在这里,但我们不需要if子句或专门针对它的类。我们可以利用构造函数参数。类似这样:
var cacaoVanillaIceCream = new CacaoIceCream(new VanillaIceCream());
cacaoVanillaIceCream.Make();
组合的好处在于,我们可以根据需要合成任意数量的对象。需要4种口味?只需编写一个构造函数链即可。这称为装饰器模式。你可以在 这里 读更多关于它的内容。
请注意, IMakeIceCream 参数是可选的。这使我可以组合使用或单独使用该类。
像这样编写代码会实现一个可拔插式的体系结构。这是非常 nice 的,因为我们可以通过编写代码来添加新功能,而不更改现有功能。任务完成。
注意一下咯:由于篇幅原因,小编已将JAVA以及JAVA相关的知识都集结整理出来了,有需要的私信我关键词 “Java”,回复获取免费下载原文件的方式。
作者:maybelence
链接:https://juejin.cn/post/6952789900114690078
来源:掘金
猜你喜欢
- 2024-11-02 Java,设计模式,七大原则,里氏替换原则(LSP),案例
- 2024-11-02 软件设计七大原则,程序员标配(一)
- 2024-11-02 设计模式七大原则 设计模式七大原则包括
- 2024-11-02 Java设计模式的精神领袖:开闭原则
- 2024-11-02 设计模式第2招第3式之命令模式 命令模式定义
- 2024-11-02 Java代码优化六大原则 java代码优化六大原则有哪些
- 2024-11-02 一天一个设计模式——软件架构设计七大原则
- 2024-11-02 专门画了9张图,搞懂设计模式6大原则,这次应该可以了吧
- 2024-11-02 程序员应知道这十大面向对象设计原则
- 2024-11-02 3张图说清楚:java设计模式原则:开闭、接口隔离、迪米特法则
你 发表评论:
欢迎- 04-11Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- 04-11Java中你知道几种从字符串中找指定的字符的数量
- 04-11探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- 04-11Python字符串详解与示例(python字符串的常见操作)
- 04-11java正则-取出指定字符串之间的内容
- 04-11String s1 = new String("abc");这句话创建了几个字符串对象?
- 04-11java判断字符串中是否包含某个字符
- 04-11关于java开发中正确的发牌逻辑编写规范
- 最近发表
-
- Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- Java中你知道几种从字符串中找指定的字符的数量
- 探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- Python字符串详解与示例(python字符串的常见操作)
- java正则-取出指定字符串之间的内容
- String s1 = new String("abc");这句话创建了几个字符串对象?
- java判断字符串中是否包含某个字符
- 关于java开发中正确的发牌逻辑编写规范
- windows、linux如何后台运行jar(并且显示进程名)
- 腾讯大佬私人收藏,GitHub上最受欢迎的100个JAVA库,值得学习
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)