用简单的例子说明提升可复用性的设计模式
此文寫給想要理解設(shè)計(jì)模式,但認(rèn)為《設(shè)計(jì)模式》中所舉的例子過于復(fù)雜的讀者。
為了使程序具有更高的可復(fù)用性,前人推薦我們使用如下設(shè)計(jì)模式:
結(jié)構(gòu)型模式:通過組合類和對象來獲得更大的結(jié)構(gòu)
1. 適配器模式(Adapter)
2. 裝飾器模式(Decorator)
3. 外觀模式(Facade)
行為模式:涉及到算法和對象間職責(zé)的分配
1. 策略模式(Strategy)
2. 模板模式(Template)
3. 迭代器模式(Iterator)
1.1 適配器模式
客戶想要根據(jù)半徑計(jì)算圓的面積(他自己不會算,甚至不知道圓是什么)
public static void main(String[] args) {double radius = 5;System.out.println("我要怎么辦?");}翻了翻代碼庫,發(fā)現(xiàn)有一個(gè)已有的工具類Mathematician里有一個(gè)計(jì)算圓的面積的方法
public class Mathematician {/*** 根據(jù)圓的周長計(jì)算面積* @param C 圓的周長* @return 圓的面積*/public double CalculateAreaFromPerimeter(double C) {return Math.pow((C / Math.PI / 2), 2) * Math.PI;}}客戶感到非常絕望,給我打了電話,拜托我給他設(shè)計(jì)一個(gè)新的工具類,能夠?qū)崿F(xiàn)用半徑算面積的功能。
可問題是我并不會算圓的面積,只會根據(jù)半徑算周長。于是我只好想方設(shè)法利用已有的方法了。
public class SuperMathTool {private final Mathematician mathematician;/*** 構(gòu)造方法,需要使用舊的工具類來完成組合* @param mathematician 一個(gè)舊的工具類對象*/public SuperMathTool(Mathematician mathematician) {this.mathematician = mathematician;}/*** 根據(jù)半徑算圓的周長* @param radius 半徑* @return 圓的周長*/public double CalculateAreaFromRadius(double radius) {double perimeter = 2 * radius * Math.PI;double ans = mathematician.CalculateAreaFromPerimeter(perimeter);return ans;}}這樣還不算完工。再用接口包裝一下。
public interface MathTool {public double CalculateAreaFromRadius(double radius);}還要記得讓SuperMathTool實(shí)現(xiàn)這個(gè)接口。這樣就算完成了。
為什么要用接口呢?我可以寫詳細(xì)的Javadoc方便用戶使用,也方便用戶以后擴(kuò)展,還規(guī)定了一個(gè)優(yōu)秀的數(shù)學(xué)工具類必須有根據(jù)圓的半徑計(jì)算面積的功能。
為什么要用組合(委派)呢?因?yàn)檫@樣的話,除了計(jì)算半徑以外的其他工具方法,都可以委派給舊工具箱類完成,我自己不用重寫實(shí)現(xiàn),也不用復(fù)制代碼。
(用繼承的方法也可以實(shí)現(xiàn)同樣的功能)
最后,我告訴用戶,我的工具不能單獨(dú)使用,想要使用的話就要這樣:
public static void main(String[] args) {double radius = 5;MathTool tool = new SuperMathTool(new Mathematician());tool.CalculateAreaFromRadius(radius);}1.2 裝飾器模式
客戶想要根據(jù)邊長計(jì)算正方形的面積(他自己不會算,甚至不知道正方形是什么)
public static void main(String[] args) {double sideLength = 5;System.out.println("怎么辦啊?");}翻了翻代碼庫,發(fā)現(xiàn)有一個(gè)已有的數(shù)學(xué)工具類接口
public interface MathTool {/*** 根據(jù)半徑計(jì)算圓的面積* @param radius 半徑* @return 圓的面積*/public double CalculateAreaFromRadius(double radius);}和它的一個(gè)實(shí)現(xiàn)類
public class Mathematician implements MathTool {@Overridepublic double CalculateAreaFromRadius(double radius) {double ans = Math.pow(radius, 2) * Math.PI;return ans;}}這東西只能算圓的面積啊!客戶感到非常絕望,給我打了電話,拜托我給他設(shè)計(jì)一個(gè)新的工具類,能夠?qū)崿F(xiàn)用邊長算正方形面積的功能,同時(shí)不要把舊功能丟掉。
那么我要開始工作了:寫一個(gè)新的抽象類實(shí)現(xiàn)原接口(防止遺漏原有方法),然后用舊工具類組合,將原有方法委派給它
public abstract class MathToolDecorator implements MathTool {private final MathTool oldTool;/*** 構(gòu)造方法:完成組合* @param mathematician 舊工具箱對象*/public MathToolDecorator(Mathematician mathematician) {this.oldTool = mathematician;}/*** 原有的方法委派給舊工具箱*/@Overridepublic double CalculateAreaFromRadius(double radius) {return oldTool.CalculateAreaFromRadius(radius);}/*** 根據(jù)邊長計(jì)算正方形面積* @param sideLength 邊長* @return 正方形面積*/public abstract double CalculateAreaFromSideLength(double sideLength);}使用抽象類的原因是,計(jì)算正方形面積的新方法可能有很多種實(shí)現(xiàn),這樣寫方便以后擴(kuò)展;并且,除了新方法之外的部分已經(jīng)在抽象類中寫好了,一勞永逸,以后寫不同實(shí)現(xiàn)時(shí)就只用寫正方形,原有方法就不用再寫了(等下就能看到)。
接下來,在具體類中完成計(jì)算正方形面積的實(shí)現(xiàn):
public class ConcreteMathToolDecorator extends MathToolDecorator {/*** 構(gòu)造方法:直接調(diào)用抽象類構(gòu)造方法即可* @param mathematician*/public ConcreteMathToolDecorator(Mathematician mathematician) {super(mathematician);}@Overridepublic double CalculateAreaFromSideLength(double sideLength) {double ans = Math.pow(sideLength, 2);return ans;}}這樣就算完成了。最后告訴用戶應(yīng)該這樣使用:
public static void main(String[] args) {double sideLength = 5;MathToolDecorator tool = new ConcreteMathToolDecorator(new Mathematician());tool.CalculateAreaFromSideLength(sideLength);}1.3 外觀模式
用戶想要計(jì)算a * b mod c,他想要調(diào)用已有的方法來完成這個(gè)復(fù)雜的計(jì)算。他翻了翻代碼庫,找到了這個(gè)工具類:
這些計(jì)算非常精妙,但想要完成計(jì)算必須反復(fù)調(diào)用它們很多次,實(shí)在是太繁瑣了!用戶覺得懶得動彈,于是給我打了電話,讓我寫一個(gè)方便計(jì)算a * b mod c的工具類。
這不難做,把繁瑣的操作包裝起來方便用戶使用,這是我的強(qiáng)項(xiàng)。
public class EasyTool {private final SuperMathTool tool = new SuperMathTool();/*** 一個(gè)簡便的操作* 計(jì)算a * b mod c*/public int calculate(int a, int b, int c) {int ans = 0;for (int i = 1; i <= b; i++) {ans = tool.plus(a, i);}ans = tool.mod(ans, c);return ans;}}最后,告訴用戶這樣使用:
public class Main {public static void main(String[] args) {int a = 53;int b = 97;int c = 89;EasyTool tool = new EasyTool();tool.calculate(a, b, c);}}兩行即可解決問題!
2.1 策略模式
眾所周知,java的整數(shù)乘法乘法有兩種實(shí)現(xiàn)方式,一種是直接乘起來,另一種是累加。
大豬頭最近正在設(shè)計(jì)一個(gè)數(shù)學(xué)工具類,其中有一個(gè)方法就是計(jì)算乘法。
public class MathTool {/*** 整數(shù)加法*/public int plus(int a, int b) {return a + b;}/*** 整數(shù)乘法*/public int multiply(int a, int b) {return a * b;}}但他認(rèn)為這不夠完美,因?yàn)樗麑?shí)現(xiàn)乘法的方式是固定的。他有一個(gè)想法:讓用戶在運(yùn)行時(shí),動態(tài)地決定用哪種實(shí)現(xiàn)方式來計(jì)算乘法。于是我建議他使用策略模式:
1. 首先寫一個(gè)策略接口,聲明一下將要完成什么工作
public interface Strategy {/*** 計(jì)算整數(shù)乘法a * b* @param a* @param b* @return a * b*/public int multiply(int a, int b);}2. 寫出兩種不同的實(shí)現(xiàn)
/*** 乘法的第一種實(shí)現(xiàn)*/ public class Strategy1 implements Strategy {@Overridepublic int multiply(int a, int b) {int ans = a * b;return ans;}} /*** 乘法的第二種實(shí)現(xiàn)*/ public class Strategy2 implements Strategy {@Overridepublic int multiply(int a, int b) {int ans = 0;for (int i = 1; i <= b; i++) {ans += a;}return ans;}}3. 將Strategy組合到原有的數(shù)學(xué)工具類中:
public class MathTool {private final Strategy strategy;/*** 構(gòu)造方法:完成組合* @param strategy 將要使用的乘法策略*/public MathTool(Strategy strategy) {this.strategy = strategy;}然后計(jì)算乘法的部分委派給strategy完成
/*** 整數(shù)加法*/public int plus(int a, int b) {return a + b;}/*** 整數(shù)乘法*/public int multiply(int a, int b) {return strategy.multiply(a, b);}}完成啦。
這樣,用戶使用MathTool的自由度就更大啦,比如:
public static void main(String[] args) {MathTool tool1 = new MathTool(new Strategy1());tool1.multiply(3, 4);MathTool tool2 = new MathTool(new Strategy2());tool2.multiply(5, 6);}2.2 模板模式
大豬頭打算設(shè)計(jì)一系列游戲,我建議他采用模板模式,因?yàn)楸M管游戲內(nèi)容不同,所有游戲都有相同的三個(gè)步驟:
1. 初始化
2. 開始
3. 結(jié)束
不同游戲的區(qū)別在于,這三個(gè)步驟的具體實(shí)現(xiàn)是不同的,但不同的游戲都必須按這個(gè)順序執(zhí)行這三個(gè)步驟,這個(gè)順序是固定的。
先寫一個(gè)游戲的抽象類:
public abstract class Game {/*** 模板方法:按照特定順序調(diào)用其他方法完成操作* 注意:加上final標(biāo)簽防止被重寫*/public final void play(){//初始化游戲 initialize();//開始游戲 startPlay();//結(jié)束游戲 endPlay();}abstract void initialize();abstract void startPlay();abstract void endPlay();}它有一個(gè)final的模板方法,規(guī)定了各個(gè)操作的順序,要求無論什么樣的實(shí)現(xiàn)都必須按照這個(gè)順序執(zhí)行。
它的不同實(shí)現(xiàn)需要重寫初始化、開始、結(jié)束的操作:
/*** 足球游戲*/ public class Football extends Game {@Overridevoid initialize() {System.out.println("足球游戲初始化中...");}@Overridevoid startPlay() {System.out.println("足球游戲開始!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}@Overridevoid endPlay() {System.out.println("足球游戲結(jié)束!");}} /*** 拳擊游戲*/ public class Boxing extends Game {@Overridevoid initialize() {System.out.println("拳擊游戲初始化中...");}@Overridevoid startPlay() {System.out.println("拳擊游戲開始!");try {Thread.sleep(1500);} catch (InterruptedException e) {e.printStackTrace();}}@Overridevoid endPlay() {System.out.println("拳擊游戲結(jié)束!");}}用戶使用的方法如下:
public static void main(String[] args) {Game game1 = new Football();game1.play();Game game2 = new Boxing();game2.play();}2.3 迭代器模式
大豬頭設(shè)計(jì)了一個(gè)模擬行星系中行星運(yùn)動的軌道系統(tǒng):
public class CircularOrbit {private Map<Double, String> objects = new HashMap<>();// AF// objects是軌道系統(tǒng)各物體按照其軌道半徑的索引? //? ?Map<Double, String>中,Double是軌道半徑,String是物體名稱
為了方便地訪問各個(gè)軌道物體(并且最好能從軌道半徑從小到大訪問),他需要給這個(gè)ADT實(shí)現(xiàn)Iterable接口,
public class CircularOrbit implements Iterable {然后實(shí)現(xiàn)Iterator方法:
@Overridepublic Iterator<String> iterator() {return new OrbitIterator(this);}這個(gè)OrbitIterator是自己定義的一個(gè)迭代器類(當(dāng)然了,Java可不知道我們會設(shè)計(jì)怎樣的ADT,當(dāng)然不可能給我們準(zhǔn)備好Iterator)
最好能寫成內(nèi)部類,更加安全。以下展示CircularOrbit的完成版本:
public class CircularOrbit implements Iterable<String> {private Map<Double, String> objects = new HashMap<>();// AF// objects是軌道系統(tǒng)各物體按照其軌道半徑的索引// Map<Double, String>中,Double是軌道半徑,String是物體名稱 @Overridepublic Iterator<String> iterator() {return new OrbitIterator(this);}class OrbitIterator implements Iterator<String> {private final List<String> objs = new ArrayList<>();private int index;// AF// objs按從里向外的順序儲存軌道系統(tǒng)中的軌道物體// index表示當(dāng)前遍歷到的元素在list中的下標(biāo)// RI// index >= 0// Safety// index是private的不可變類// objs是private final的可變類,且沒有操作向外返回該對象/*** 構(gòu)造方法:將CircularOrbit中的物體有里向外存在list中* @param c 軌道系統(tǒng)對象*/private OrbitIterator(CircularOrbit c) {List<Double> radiuses = new ArrayList<>();radiuses.addAll(c.objects.keySet());radiuses.sort(Comparator.naturalOrder());for (int i = 0; i < radiuses.size(); i++) {objs.add(c.objects.get(radiuses.get(i)));}index = 0;}@Overridepublic boolean hasNext() {if (index < objs.size()) return true;elsereturn false;}@Overridepublic String next() {return objs.get(index);}}}注意:Iterable和Iterator后的尖括號中寫的是想要讓Iterator.next()返回的對象類型。
用戶使用方法如下:
public static void main(String[] args) {CircularOrbit c = new CircularOrbit();Iterator<String> it = c.iterator();while (it.hasNext()) System.out.println(it.next());}舉例就到此結(jié)束了。
為了方便理解,選用了過分簡單的例子,請多多包涵。
?
轉(zhuǎn)載于:https://www.cnblogs.com/snt165/p/11074668.html
總結(jié)
以上是生活随笔為你收集整理的用简单的例子说明提升可复用性的设计模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 联想手机怎么重装系统教程视频教程 联想手
- 下一篇: win10怎么解决ime禁用 win10