最近新冠肺炎疫情还在持续中,身在湖北,真实的感受到疫情的严重,望大家还是不要放松警惕,尽量隔离自身,保护他人就是保护自己,切忌聚众搞事情。在家办公的这漫长的隔离阶段,我学习了设计模式的相关知识。在这里写下学习总结,记录生活。
设计模式入门
设计模式是人们在面对同类型软件工程设计问题所总结出的一些有用经验。模式不是代码,而是某类问题的通用设计解决方案。
“四人帮"Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides总结写出了《设计模式》这本书,相对来说这本书可能难懂一点,但是这本书是最经典的,大家有兴趣的可以看下。
设计模式是前人经验的总结,所以我们在做项目写代码的时候,前人发明的轮子,我们就可以直接用了。
学习设计模式的方法:在你的代码设计和以前做过的项目中去发现何处可以使用它们,复盘是很有效的一种学习方式。我想我们之前所写的代码,或多或少肯定有用到相关的设计模式,可能当时我们并不知道那就是一种设计模式。或者我们之前写的代码由于经验欠缺,导致代码设计很差,后期扩展也很烂。各位都是从新手过来的,应该能明白,去找出你认为的烂代码,去使用设计模式改善它吧,我想那样才能体会到设计模式的妙用。
设计模式的本质目的是使软件工程具有更好的:
代码重用性 (即:相同功能的代码,不用多次编写)
可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
使程序呈现高内聚,低耦合的特性
面向对象是原则,设计模式是方法和工具。
策略模式
使用继承的方式
项目经理小为最近接到一个项目,记录鸡的行为(邻居家的鸡满天飞,正好适合调研)。调研一段时间后发现,鸡子有些共同的地方,会咯咯叫、会游泳(小时候经常见到)。这两个功能放到一个父类里面,子类里面就不需要重复了。鸡子还分为家鸡和野鸡等,这个可以在父类中作为抽象方法。需求确定了,代码如下:
父类
public abstract class Chicken {
public Chicken(){}
/**
* 鸣叫
*/
public void cluck(){
System.out.println("gege~~");
}
/**
* 游泳
*/
public void swim(){
System.out.println("I will swim");
}
/**
* 类别
*/
public abstract void type();
}
父类里面有三个方法,之前调研的鸣叫、游泳、类型。
家鸡:
/**
* 家鸡
*/
public class DomesticChicken extends Chicken{
@Override
public void type() {
System.out.println("I am DomesticChicken");
}
}
野鸡:
/**
* 野鸡
*/
public class Pheasant extends Chicken{
@Override
public void type() {
System.out.println("I am Pheasant");
}
}
测试类:
public class TestChicken {
public static void main(String[] args) {
DomesticChicken domesticChicken = new DomesticChicken();
Pheasant pheasant = new Pheasant();
domesticChicken.type();
domesticChicken.cluck();
domesticChicken.swim();
System.out.println("-------------");
pheasant.type();
pheasant.cluck();
pheasant.swim();
}
}
输出结果如下:
I am DomesticChicken
gege~~
I will swim
-------------
I am Pheasant
gege~~
I will swim
以上代码,就是使用的面向对象中的继承方式,已经满足了项目基本需求。
过段时间后,又有新的需求提出了,张三提出需要添加会飞的鸡子。小为想了下,将会飞添加在父类里面,添加代码如下:
/**
* 飞
*/
public void fly(){
System.out.println("I will fly");
}
执行测试类后:
I am DomesticChicken
gege~~
I will swim
I will fly
-------------
I am Pheasant
gege~~
I will swim
I will fly
看起来没毛病,但仔细一想,小为觉得并不是所有的鸡子都会飞,比如说肉鸡。这样一来,代码添加在父类里面,会让子类都会飞了,这是不科学的。
继承的问题:对类的局部改动,尤其是父类的局部改动,会影响其他部分,影响还有溢出效应。
接下来小为依然用面向对象的思维来解决所有鸡子会飞的问题,在子类肉鸡中覆盖掉fly方法,代码如下:
/**
* 肉鸡
*/
public class Broiler extends Chicken{
@Override
public void type() {
System.out.println("I am Broiler");
}
/**
* 重写父类的fly方法
*/
public void fly(){
System.out.println("I will not fly");
}
}
执行测试类结果如下:
I am Broiler
gege~~
I will swim
I will not fly
这样一看问题解决了,没啥毛病,但是仔细一想,如果后面再来几十种不会飞的鸡子呢,那岂不是每个子类都必须重写父类的fly方法了,这样会导致代码重复而且工作量大。
这时估计会有人说,没关系,把父类fly方法改成抽象方法,让子类去实现对应的抽象方法,需要飞的去实现,不需要非得就不用,那不就解决了之前的问题。
这样又会出现一个问题,就是如果有十种鸡子都会飞,那岂不是每个子类都要写相同的代码,降低了代码的复用性。所以继承的方式并不能完全的解决子类的问题。
如果此时又有新需求进来,来了个石鸡,既不会叫也不会飞,更不会游泳了。父类里面挖的一个坑,每个子类都要来填,增加工作量,复杂度O(N^2),这明显不是好的设计方式。
使用策略模式的方式
下面我们来分析下,遇到类似的问题,需要找到项目中变化和不变化的部分,提取变化的部分,抽象成接口+实现。鸡子的飞、叫、游泳都是会变化的部分。这些变化的行为可以抽象成一个行为族,将飞行、叫抽象成行为接口。
如上图。飞行行为接口,有对应的会飞和不会飞实现类。鸣叫行为接口,有对应的会叫不会叫实现类。这样的好处:新增行为简单,行为类更好的复用,组合更方便。比如你在新增个嘎嘎叫的鸡子(野鸡是这样叫的),直接可以实现CluckInterface接口就行了。重新设计项目代码如下:
鸣叫行为抽象为接口:
/**
* 鸣叫接口
*/
public interface CluckInterface {
public void cluck();
}
guoguo叫(家鸡):
/**
* guoguo叫实现类
*/
public class GuoGuoCluckImpl implements CluckInterface {
@Override
public void cluck() {
System.out.println("guoguo~~~");
}
}
gaga叫(野鸡):
/**
* gaga叫
*/
public class GaGaCluckImpl implements CluckInterface {
@Override
public void cluck() {
System.out.println("gaga~~~");
}
}
飞行行为抽象为接口:
/**
* 飞行接口
*/
public interface FlyInterface {
public void fly();
}
会飞:
/**
* 会飞行实现类
*/
public class FlyImpl implements FlyInterface {
@Override
public void fly() {
System.out.println("I will fly");
}
}
不会飞:
/**
* 不会飞行实现类
*/
public class NotFlyImpl implements FlyInterface {
@Override
public void fly() {
System.out.println("I will not fly");
}
}
父类:
public abstract class Chicken {
CluckInterface cluckInterface;
FlyInterface flyInterface;
public Chicken(){}
/**
* 鸣叫
*/
public void cluck(){
cluckInterface.cluck();
}
/**
* 飞
*/
public void fly(){
flyInterface.fly();
}
/**
* 设置鸣叫行为
*/
public void setCluckInterface(CluckInterface ci){
cluckInterface = ci;
}
/**
* 设置飞行行为
*/
public void setFlyInterface(FlyInterface fi){
flyInterface = fi;
}
/**
* 类别
*/
public abstract void type();
/**
* 游泳
*/
public void swim(){
System.out.println("I will swim");
}
}
有看到父类里面有设置行为的方法,这个方法就是用来设置不同的行为来组合行为的,动态的来改变鸡子的行为。
家鸡子类:
/**
* 家鸡
*/
public class DomesticChicken extends Chicken{
public DomesticChicken(){
cluckInterface = new GuoGuoCluckImpl();
flyInterface = new NotFlyImpl();
}
@Override
public void type() {
System.out.println("I am DomesticChicken");
}
}
野鸡子类:
/**
* 野鸡
*/
public class Pheasant extends Chicken{
public Pheasant(){
flyInterface = new FlyImpl();
cluckInterface = new GaGaCluckImpl();
}
@Override
public void type() {
System.out.println("I am Pheasant");
}
}
测试类:
public class TestChicken {
public static void main(String[] args) {
Chicken domesticChicken = new DomesticChicken();
Chicken pheasant = new Pheasant();
domesticChicken.type();
domesticChicken.cluck();
domesticChicken.swim();
domesticChicken.fly();
System.out.println("-------------");
pheasant.type();
pheasant.cluck();
pheasant.swim();
pheasant.fly();
}
}
执行结果:
I am DomesticChicken
guoguo~~~
I will swim
I will not fly
-------------
I am Pheasant
gaga~~~
I will swim
I will fly
可以看到结果家鸡guoguo叫,会游泳,不会飞,野鸡是gaga叫,会游泳,会飞。接下来我们再添加一个野鸡必将它设置为不会飞,guoguo叫,测试代码改变如下:
public class TestChicken {
public static void main(String[] args) {
Chicken domesticChicken = new DomesticChicken();
Chicken pheasant = new Pheasant();
domesticChicken.type();
domesticChicken.cluck();
domesticChicken.swim();
domesticChicken.fly();
System.out.println("-------------");
pheasant.type();
pheasant.cluck();
pheasant.swim();
pheasant.fly();
System.out.println("-------------");
pheasant.type();
pheasant.setCluckInterface(new GuoGuoCluckImpl());
pheasant.cluck();
pheasant.swim();
pheasant.setFlyInterface(new NotFlyImpl());
pheasant.fly();
}
}
执行结果:
I am DomesticChicken
guoguo~~~
I will swim
I will not fly
-------------
I am Pheasant
gaga~~~
I will swim
I will fly
-------------
I am Pheasant
guoguo~~~
I will swim
I will not fly
可以看到第二只野鸡的行为都改变了,结果如预期一样。
这样设置行为的方式在对象使用过程中动态的改变了行为,比原来那种继承的方式灵活多了。
原来的那种继承方式如果要进行行为的改变,就必须要重新指定一个对象,把那个功能覆盖掉,因为它把实现的代码放到了子类或者父类里面。
而现在我们是把具体的实现代码放在了行为族里面,也就是接口和实现类里面,已经把行为封装成了对象,所以可以通过设置行为的算法来改变行为。
总结一下:
策略模式:分别封装行为接口,实现算法族,父类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为算法的变化独立于算法的使用者。
写到这里,对策略模式基本上有个了解了,希望在以后的编码中有机会去用到。当然不能为了用模式而用,那样得不偿失,咱们下次再见。