日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java 设计模式——组合模式

發(fā)布時間:2024/3/24 java 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 设计模式——组合模式 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

概述

有時我們可能會被要求處理一個層級結(jié)構(gòu)明顯的對象,比如上下級的公司員工、比如層級嵌套的文件夾,還有豐富多彩的美食菜單。可是,我們可能要屢試不爽地編寫深度搜索代碼、要小心翼翼地編寫遞歸邏輯。現(xiàn)在你可以忘掉這些,學(xué)習(xí)一些新的技能,讓你秒刷副本。當(dāng)然,這句有些夸張,你可以忽略。只是它單純地表達我對本文要說的這個模式的喜歡(也有可能只是因為我工作中恰好遇到這個問題)。


組合模式

定義

將對象組合成樹形結(jié)構(gòu)以表示**“部分-整體”**的層次結(jié)構(gòu)。組合模式使得用戶對單個對象和組合對象的使用具有一致性。

情境分析

看到組合的模式的定義,或許你就知道了這里的組合與我們平常討論的*“繼承-組合”*并不是同一個概念。因為在學(xué)習(xí)組合模式之前,我就是把這兩個弄得有點混淆,所以一直沒有認真地學(xué)習(xí)過它,以為組合也就是那么回事了嘛。可是,當(dāng)我開始真的學(xué)習(xí)它的時候,才知道,這是兩回事。
定義上說的是部分與整體的層次結(jié)構(gòu),可能就這前半句還不能說明什么,不過我們可以從后半句中找到突破點,那就是單個對象與組合對象。也就是在組合對象中組合了一個部分的集合,這個集合中的元素也就是單個元素所具有的對象類型。
當(dāng)然,你不要理解成組合模式里也可以只有這個集合了。如果是這樣,那么一個 List 完全可以可以搞定,又何必搞出個模式來突顯逼格呢?
現(xiàn)在我們就來“舉個栗子”,對,是栗子。哈哈~
我現(xiàn)在是一家中式餐廳的老板了。在我的店里你可以享用早餐、中餐、晚餐。早餐就簡單點,只提供包子、油條跟豆?jié){吧(是的,這個不是主業(yè))。午餐會豐盛一些,在午餐里你們選擇北方菜或是南方菜。北方菜主要有:鍋包肉、豬肉燉粉條、辣子雞、干炸里脊、糖醋里脊、板栗燒雞、地三鮮、紅燒肉、回鍋肉、 口水雞、宮保雞丁、可樂雞翅;南方菜主要有:炒米粉、南瓜餅、南燜羊肉、蒸排骨、肉片炒青椒、水果皮薩、北炒魚香茄子、糯米糍團、芥菜煲。晚餐上可以吃得精致一些,主要有地道小吃和甜點。地道小吃:肉夾饃、羊肉泡饃、烏冬面、章魚小丸子、蔥油餅、老婆餅;甜點:冰淇淋、鮮奶蛋糕還有蜜汁藕。
把上面的描述轉(zhuǎn)換成圖片菜單就像下面這樣的:

當(dāng)然,這里并不夠齊全,只是為了照顧圖片的大小(當(dāng)然也不可否認是博主偷懶了),只繪制了其中的一部分,不過足夠我們說明問題了。

一般組合模式

模式分析

在一般組合模式里,我們只做了一件事,那就是模糊了簡單元素與復(fù)雜元素。怎么說明這一點呢?針對上面舉的例子來說,我們的每一種菜都是一個簡單元素,而每一種菜系(南方/北方/小說/甜點)或是餐飲的類型(早餐/午餐/晚餐)都是一個復(fù)雜元素,因為這里又包含若干的簡單元素。我們把菜系定義為 Menu,而每一道具體的菜則定義成 MenuItem。這樣我們就可以繪制出一般組合模式的類圖,如下:

這里面我們的菜單(Menu)和菜單項(MenuItem)都繼承自一個 MenuComponent。MenuComponent 是一個抽象的類。當(dāng) Menu 和 MenuItem 繼承自同一個類時,我們就可以實現(xiàn)模糊簡單元素與復(fù)雜元素了,因為我們可以按照處理 MenuComponent 的方式處理 Menu 和 MenuItem。這里我們只是模糊了這兩個的分界,卻不能真的等同看待。很簡單,上面的的 MenuItem 最起碼有一個價格的屬性、而 Menu 就不存在這個屬性;Menu 可以有一個 add MenuComponent 的方法,而 MenuItem 則不可能會有。
說到這里,可能你會說組合模式并不完美。是的,我也這么覺得。它讓這件事情模糊了,讓 MenuComponent 的使用產(chǎn)生了歧義。比如我們在使用它的時候,根本不知道它的某一個方法是可以正常使用。比如,一個 MenuComponent(可實際的類型可能是 Menu,而我們不知道),這時可能會調(diào)用它的 getPrice() 方法,這是有問題的,邏輯上是走不通的。那么,我們就必須要為 Menu 的這個方法拋出一個異常(異常是一個心機 boy,我們都不太喜歡它)。當(dāng)然,也可以在外部使用 instanceof 關(guān)鍵字處理。可是,這樣的處理總讓我有一種非面向?qū)ο蟮奶幚磉^程,所以還是拋出異常吧。
另外,Menu 中組合了 MenuItem,從這一點來看,倒是有幾分“繼承-組合”的意味。

邏輯實現(xiàn)

先來看看抽象類,這個是基礎(chǔ):
MenuComponent.java

public abstract class MenuComponent {public String getName() {throw new UnsupportedOperationException("暫不支持此操作");}public String getDescription() {throw new UnsupportedOperationException("暫不支持此操作");}public double getPrice() {throw new UnsupportedOperationException("暫不支持此操作");}public boolean isVegetarian() {throw new UnsupportedOperationException("暫不支持此操作");}public void print() {throw new UnsupportedOperationException("暫不支持此操作");}public void add(MenuComponent menuComponent) {throw new UnsupportedOperationException("暫不支持此操作");}public void remove(MenuComponent menuComponent) {throw new UnsupportedOperationException("暫不支持此操作");}public MenuComponent getChild(int childIndex) {throw new UnsupportedOperationException("暫不支持此操作");} }

而在 Menu 的具體類中,雖然是繼承了 MenuComponent,可是它的抽象方法又不能全部重寫。原因上面也說了,這里不贅述了。可是,由于 Java 語法的客觀存在,所以這里我們拋出了一個異常。
Menu.java

public class Menu extends MenuComponent {private String name = null;private String desc = null;private List<MenuComponent> menuComponents = null;public Menu(String _name, String _desc) {name = _name;desc = _desc;}@Overridepublic String getName() {return name;}@Overridepublic String getDescription() {return desc;}@Overridepublic void print() {System.out.println("\nMenu: { " + name + ", " + desc + " }");if (menuComponents == null) {return;}System.out.println("-------------------------");for (MenuComponent menuComponent : menuComponents) {menuComponent.print();}}@Overridepublic void add(MenuComponent menuComponent) {if (menuComponents == null) {menuComponents = new ArrayList<MenuComponent>();}menuComponents.add(menuComponent);}@Overridepublic MenuComponent getChild(int childIndex) {if (menuComponents == null || menuComponents.size() <= childIndex) {return null;}return menuComponents.get(childIndex);} }

基于上面對 Menu 類的說明,這里的 MenuItem 類的實現(xiàn)過程也是一樣:只重寫能夠重寫的部分,不能重寫的地方拋出一個異常等待上層處理。
MenuItem.java

public class MenuItem extends MenuComponent {private String name = null;private String desc = null;private boolean vegetarian = false;private double price = 0.0d;public MenuItem(String _name, String _desc, boolean _vegetarian, double _price) {this.name = _name;this.desc = _desc;this.vegetarian = _vegetarian;this.price = _price;}@Overridepublic String getName() {return name;}@Overridepublic String getDescription() {return desc;}@Overridepublic double getPrice() {return price;}@Overridepublic boolean isVegetarian() {return vegetarian;}@Overridepublic void print() {System.out.println("MenuItem: { " + name + ", " + desc + ", " + vegetarian + ", " + price + " }");} }

上面的代碼是整個一般組合模式的關(guān)鍵部分,這是需要注意的是它們的 print() 方法。對于 MenuItem 的 print() 來說,是很常規(guī)的打印,而 Menu 的打印則需要作處理。因為我們正常的理解里一個菜單因為會包含很多菜單項,所以,這里我們就把當(dāng)前菜單下的所以菜單打印一遍。不過,這也不是什么難事,因為在每份 Menu 中都有一個 MenuItem 的列表。好了,問題解決。詳情參見上面的代碼部分。

模式小結(jié)

從上面的例子也可以看出,組合模式在解決有層級關(guān)系時,有著得天獨厚的優(yōu)勢。思路清晰、代碼優(yōu)雅。唯一的不足是我們要針對不同的情況拋出相應(yīng)的異常。

組合與迭代

對于組合模式息息相關(guān)的另一種模式——迭代模式,它在組合模式中可以說有著重要的地位。在上面的代碼中,有點編程邏輯的人應(yīng)該都可以發(fā)現(xiàn),它們的 print() 方法是對象內(nèi)部的操作。也就是說,如果我想要通過一個 Menu 操作一個 MenuItem 就必須在 Menu 內(nèi)部進行實現(xiàn)。這是不現(xiàn)實的,因為需求變化的速度,可能隔了幾秒連它的親媽也不認識了。正因為如此,所以我們就必須想辦法從外部拿到 Menu 中的 MenuItem。
比如現(xiàn)在我想知道餐廳里所有的素食有哪些,如果我們不去改動原有代碼,那么就可以添加一個外部的迭代邏輯。

深搜的試水之行

或許你又會說,這里根本不需要使用迭代,用一次深搜就 OK 了。是的沒錯,而且對于一個數(shù)據(jù)結(jié)構(gòu)基本功還可以的同學(xué),可以馬上寫出一個深搜的解決方案。這里給出我的深搜方案:

public void showVegetarMenu(MenuComponent menu) {List<MenuComponent> visited = new ArrayList<>();showVegetarMenu(menu, visited);}private void showVegetarMenu(MenuComponent menu, List<MenuComponent> visited) {if (visited.contains(menu)) {return;}if (menu instanceof MenuItem) {if (menu.isVegetarian()) {System.out.println(menu);}return;}List<MenuComponent> children = ((Menu) menu).getChildren();for (int i = 0; i < children.size(); i++) {showVegetarMenu(children.get(i), visited);}}

結(jié)果不出意外。能夠使用深搜,已然是逼格滿滿了。不過,使用深搜讓我有一種面向過程編程的感覺,不夠優(yōu)雅。下面就讓我用迭代器來實現(xiàn)一次華麗的逆轉(zhuǎn)吧。

迭代的逆轉(zhuǎn)

首先我們?yōu)?MenuComponent 添加一個 createIterator() 方法。就像下面這樣:
MenuComponent.java

public abstract class MenuComponent {( ... 省略重復(fù)的 N... )public abstract Iterator<MenuComponent> createIterator(); }

由于這里添加的是一個抽象的方法,那么在 Menu 和 MenuItem 中就要必須重寫這個 createIterator() 方法。
Menu.java

public class Menu extends MenuComponent {private CompositeIterator iterator = null;( ... 省略重復(fù)的 N... )@Overridepublic Iterator<MenuComponent> createIterator() {if (iterator == null) {iterator = new CompositeIterator(menuComponents.iterator());}return iterator;} }

MenuItem.java

public class MenuItem extends MenuComponent {( ... 省略重復(fù)的 N... )@Overridepublic Iterator<MenuComponent> createIterator() {return new NullIterator();} }

在上面兩段代碼中提到了兩個迭代器類:CompositeIterator、NullIterator。這里有參照書本上的邏輯,不過也有改動,因為書本的迭代器沒有通用性,下面會對這一點進行說明的。
CompositeIterator.java

public class CompositeIterator implements Iterator<MenuComponent> {private Stack<Iterator> stack = new Stack<>();public CompositeIterator(Iterator iterator) {stack.push(iterator);}@Overridepublic boolean hasNext() {if (stack.empty()) {return false;}Iterator iterator = stack.peek();if (!iterator.hasNext()) {stack.pop();return hasNext();}return true;}@Overridepublic MenuComponent next() {if (hasNext()) {Iterator iterator = stack.peek();MenuComponent component = (MenuComponent) iterator.next();if (component instanceof Menu) {Iterator menuIterator = component.createIterator();if (!stack.contains(menuIterator)) {stack.push(menuIterator);}}return component;}return null;}@Overridepublic void remove() {throw new UnsupportedOperationException();} }

這里的棧結(jié)構(gòu)使用得很巧妙,因為這個棧的使用讓我想到在 LeetCode 上的一道算法題,也是使用棧來實現(xiàn),而且比一般的算法復(fù)雜度低很多,如果我不犯懶的話,應(yīng)該會寫那一篇博客的。咳咳,扯遠了,回到正題。有關(guān)于棧的使用是一些數(shù)據(jù)結(jié)構(gòu)和 Java api 的基礎(chǔ),這里不多說什么了。還有這里的 hasNext() 和 next() 方法,這里要求你對數(shù)據(jù)結(jié)構(gòu)和 Java api(主要是 Stack 這一塊)比較熟悉。所以,如果你看到這個地方有什么不太理解的,可以留言,也可以自行復(fù)習(xí)一下這兩塊內(nèi)容。

NullIterator.java

public class NullIterator implements Iterator<MenuComponent> {@Overridepublic boolean hasNext() {return false;}@Overridepublic MenuComponent next() {return null;}@Overridepublic void remove() {throw new UnsupportedOperationException();} }

因為每個菜單項都不可能什么子菜單項,也就不存在什么迭代器了,所以在 MenuItem 中就可以返回一個 Null 的迭代器。當(dāng)然,這是理想的做法。你也可以直接返回 null,只是這樣一來,在上層就要多一次判空處理,相比較而言,這樣的實現(xiàn)更優(yōu)雅。
程序的結(jié)果自然不出所料:

素食菜單(迭代) MenuItem: { 包子, bun, true, 1.5 } MenuItem: { 油條, fritters, true, 1.2 } MenuItem: { 豆?jié){, milk, true, 2.0 } MenuItem: { 炒米粉, Fried noodles, true, 8.0 } MenuItem: { 冰淇淋, ice cream, true, 5.0 }

只是,如果你只采用書本上的迭代器來實現(xiàn),就會出現(xiàn)多級菜單下的菜單項被 show 了 N 遍。而你只能一臉懵逼。


總結(jié)

以上是生活随笔為你收集整理的Java 设计模式——组合模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。