最近在拜读《大话设计模式》这本书,作为一个小白中的小白,读完以后感触还是很深的。再次做一个学习的记录,同时也算给自己多一次实践的机会。
相信大家在上学时都写过“简易计算器”,就是要求很简单的键盘输入两个数字,然后再给定一个符号,去做加减乘除运算。最简单的计算器实现,实在是大学老师最爱的练习小demo之一呢hhhh
大家思路都差不多,这不是直接一个switch就解决了吗?这不是有手就行?立马感觉自己能进BAT了呢hhhh(瞎掰的)
public static void main(String[] args) {
//假装这是用户输入的东西
Double num = null;
Double otherNum = null;
String operator = "*";
Double result = null;
//使用switch进行比对
switch (operator){
case "+":
result = num+otherNum;
break;
case "-":
result = num-otherNum;
break;
case "*":
result = num*otherNum;
break;
case "/":
//判断被除数不能为0
if (otherNum != 0){
result = num/otherNum;
}
break;
}
}
这样写就存在两个问题。
1.如果用户输出的是"/",那么前三次对比就相当于做了无用功,虽然在这种微乎其微的程序上没影响,但是一旦数量多了起来那就难以想象了。
2.从面向对象的设计原则上说,其实有违开闭原则。
开闭原则:对扩展开放,对修改封闭
以上述例子来说就是,允许增加新的计算方式,比如增加取余运算。但是原则上不允许修改原有的计算方式(加减乘除)!当然也不会有人有病去瞎改hhh,但是指不定手滑呢,就可能会导致原有的代码运行出问题。
于是2.0版本就应运而生!
脑子一转,那可以考虑工具类。将业务逻辑部分进行封装。这样就遵循开闭原则了吧,至少是部分遵守了hhhh
public class OperationUtils {
public static Double GetResult(double num,double otherNum,String operator){
Double result = null;
switch (operator){
case "+":
result = num+otherNum;
break;
case "-":
result = num-otherNum;
break;
case "*":
result = num*otherNum;
break;
case "/":
//判断被除数不能为0
if (otherNum != 0){
result = num/otherNum;
}
break;
}
return result;
}
}
但是这也只是仅仅达到了封装的目的,如果按照我们以上的设想,当我们需要增加运算方式时,其实还是要去修改工具类里的东西,所以根本上还是没有遵守开闭原则。
于是v3.0版本应运而生..........
首先设计一个计算的接口,其中只有一个得到计算结果的方法。至于如何实现,就根据不同实现类的计算方式即可。
计算操作接口
public interface Operation {
/**
* 计算结果的方法
* @param num
* @param otherNum
* @return
*/
Double getResult(double num,double otherNum);
}
加法实现类
/**
* 加法实现类
*/
public class AddOperation implements Operation{
//重写加法的计算方法
@Override
public Double getResult(double num, double otherNum) {
Double result = null;
result = num + otherNum;
return result;
}
}
减法实现类
/**
* 减法实现类
*/
public class SubOperation implements Operation {
//重写减法的计算方法
@Override
public Double getResult(double num, double otherNum) {
Double result = null;
result = num - otherNum;
return result;
}
}
剩余的实现类相信各位大佬肯定知道咋写了吧,这里本人就不一一展示了,就当给各位练练手吧(有懒狗,我不说是谁)
主函数
public static void main(String[] args) {
//假装这是用户输入的东西
Double num = null;
Double otherNum = null;
String operator = "*";
Double result = null;
//加法实现
AddOperation addOperation = new AddOperation();
result = addOperation.getResult(num,otherNum);
//减法实现
SubOperation subOperation = new SubOperation();
result = subOperation.getResult(num,otherNum);
}
至此,看起来就比较符合开闭原则了。
如果还需要增加计算方式,只需要实现计算方法接口即可。在增加时,因为根本不是同一个实现类,也就不要担心手滑碰到原有的代码从而导致被老板开除hhhh。
这也就是大家常说的面向接口编程,可以通俗理解为只将方法暴露在外,实际实现的逻辑其实在不同的实现类中。面向接口编程最早的体现(针对我来说)——为啥在三层架构中,业务逻辑层为啥要弄个Interface,然后又要搞个实现类去重写方法。这不是脱裤子放屁吗?我直接写我需要的方法不就好了。
现在想想当初的自己还是天真了hhhh,不得不感叹面向对象的博大精深啊...
另外诸如此类的实现方法,也体现了设计模式的策略模式。
通俗理解:将不同的算法(加法,减法,乘法,除法)进行封装,这就可以让不同的地方使用相同的算法。也就是我那一天心血来潮新写了计算器plus,我就不用再把加减乘除的方法重新写一遍,直接把之前封装好的拿过来用就好了。(这样也避免了大量分支判断的存在,导致整个代码显得非常臃肿并且后期难以维护和修改)
因为本小弟也是初学者,理解得可能不是很透彻,有兴趣的hxd往这走
有趣的设计模式——解救抓狂的商场收银员
以为3.0就完结了?必定不可能!其实在计算方面确实做的差不多了(以我的能力而言),4.0更多的是针对现有代码基础的优化。
我们虽然写了不同的计算实现类,但是在每次需要使用的时候,都要new一个对应实现类的对象,然后再调用getResult()方法进行计算。这样计算的东西少一点还好,要是用户加减乘除挨个来一遍,再倒着来一遍,那岂不是new对象都要花费很多时间???
通俗理解:我们需要一个各种实现类的集中制造基地,我们只要告诉工厂我要加法或者减法,它就直接给我们即可,不需要我们繁琐的自己去new一个新的对象。
简单计算实现类工厂
public class OperationFactory {
/**
* 获取实现类对象方法
* @param operator
* @return
*/
public static Operation createOperation(String operator){
Operation operation = null;
//看运算符来判断需要生成的实现类
switch (operator){
case "+":
operation = new AddOperation();
break;
case "-":
operation = new SubOperation();
break;
case "*":
operation = new MultiplicaOperation();
break;
case "/":
operation = new DivOperation();
break;
}
return operation;
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------更新于2022.09.26----------------------------------------------------------
最近看到一个新的写法,就此记录下。首先我们分析下现有的简单工厂,主要存在两个问题。
1.代码入侵性比较强,每次在添加新的“计算方法”的时候,都要在OperationFactory类中进行新增,其实是有违开闭原则的。
2.多分支的判断,导致做了很多无用功。比如说:当前需要除法,但是必须case到最后一个分支才能得到,也就是前三次对比其实是无用功。
那么我们考虑通过反射来进行代码的优化
创建一个相关配置文件,后期更加方便修改。其中存对应的包名。
更改简单工厂类
public class OperationFactory {
public static Operation createOperation(String name){
//首先读取配置文件,获取对应包名
Properties properties = new Properties();
Class> cls = null;
Operation operation = null;
try {
//读取配置文件
properties.load(new FileInputStream("src/main/resources/name.properties"));
//获取包名
String className = properties.getProperty(name);
//通过反射实例化对象
cls = Class.forName(className);
operation = (Operation) cls.newInstance();
}catch (Exception e){
System.out.println("处理异常...");
}
return operation;
}
}
测试类
public class Test {
public static void main(String[] args) {
//假装这是用户输入的东西
Double num = 10.5;
Double otherNum = 2.5;
Double result = null;
//加法实现
Operation addOperation = OperationFactory.createOperation("Add");
result = addOperation.getResult(num,otherNum);
System.out.println("加法计算结果:"+result);
//减法实现
Operation subOperation = OperationFactory.createOperation("Sub");
result = subOperation.getResult(num,otherNum);
System.out.println("减法计算结果:"+result);
}
}
结果展示
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------更新于2022.09.27-----------------------------------------------------
昨天的我无法理解工厂方法模式,今天的我突然茅塞顿开,再次记录一下。
工厂方法模式是对简单工厂的再一次抽象,克服了简单工厂的一些缺点。
举个栗子来说
=========================================================================
简单工厂模式:一个手机工厂同时造安卓手机(对象)和水果手机(对象)
工厂方法模式:手机工厂分为安卓手机厂(工厂)和水果手机厂(工厂),安卓手机厂只造安卓手机,水果厂只造水果手机
=========================================================================
书接上文,直接上代码。
1.首先准备一个公共工厂接口,需要实现创建实例的方法
public interface Factory {
Operation createOperation();
}
2.加法工厂的实现,通过实现工厂接口从而重写方法
public class AddOperationFactory implements Factory{
@Override
public Operation createOperation() {
AddOperation addOperation = new AddOperation();
return addOperation;
}
}
减法工厂,乘法工厂,除法工厂以此类推,就不(有)演(懒)示(狗)了。
1.简单工厂其实是违背开闭原则的,因为每次新增创建实例的判断,其实都需要在原来的地方进行修改。而工厂方法只是实现接口,也就是不会修改原有的代码,只是做新增。
2.简单工厂将创建什么的实例(加法还是减法)的逻辑判断放在了内部,而工厂方法其实把这个判断移到了客户端进行,只有在创建时由客户端进行判断。
示例:
简单工厂测试类
public class Test {
public static void main(String[] args) {
//假装这是用户输入的东西
Double num = 10.5;
Double otherNum = 2.5;
Double result = null;
//加法实现
//用户只负责讲运算符传入,具体实例哪个对象在内部进行判断
Operation addOperation = OperationFactory.createOperation("Add");
result = addOperation.getResult(num,otherNum);
System.out.println("加法计算结果:"+result);
}
}
工厂方法测试类
public class Test {
public static void main(String[] args) {
//假装这是用户输入的东西
Double num = 10.5;
Double otherNum = 2.5;
Double result = null;
//在客户端时,就知道需要创建的是加法工厂,而不是在内部进行判断
Factory factory = new AddOperationFactory();
Operation addOperation = factory.createOperation();
result = addOperation.getResult(num,otherNum);
}
}
工厂方法模式后续优化同样可以考虑由反射进行实例创建,这里留个坑过后补上。
任何设计模式没有绝对的好与坏,只看那种情况下的适配性最好,什么时候是最佳选择!
至此真的完结了,仅仅看了第一小节,感觉腰不疼了,腿不酸了呢,实乃上古神书之一!
以上纯属瞎掰。
分享书中一句话:编程是一门技术,更是一门艺术。
曾经我就很好奇,大家都是流水线式的逻辑进行业务的实现,这还能分出好坏?现在看来实在是当初自己太年轻了hhh。
如有问题,请各位大佬指正。-----记一个菜狗的学习记录