9.1 公司層級結構
公司中的職位結構,就像一個金字塔,看如下管理結構圖:
在上圖中我們看到,這是典型的樹形結構。每一個職位上的人都有與其直接打交道的人,而不用關心與其職位相關較大的人。
9.2 模式定義
組合模式(Composite Pattern),將對象組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
組合模式有時又叫部分-整體模式,它使我們在樹形結構的問題中,模糊了簡單元素和復雜元素的概念,客戶程序可以像處理簡單元素一樣來處理復雜元素,從而使得客戶程序與復雜元素的內部結構解耦。
組合模式讓你可以優化處理遞歸或分級數據結構。有許多關於分組數據結構的例子,使得組合模式非常有用武之地。關於分級數據結構的一個普遍性的例子是你每次使用計算機時所遇到的——文件系統。文件系統由目錄和文件組成,每個目錄都可以裝內容,目錄的內容可以是文件,也可以是目錄。按照這種方式,計算機的文件系統就是以遞歸結構來組織的。如果你想描述這樣的數據結構,那么你可以使用組合模式。
9.3 一般化分析
組合模式講的是整體和部分的關系,下面,我們就來看一下公司層級結構的例子。
在圖9-1中,我們可以看到存在兩種節點,一種是具有下屬的節點,如CEO、部門經理、技術主管等,另一種就是實際干活的人,如部門助理、軟件工程師。
根據這兩種節點,我們先來定義兩種角色:
1)管理者角色
2)普通員工角色
其結構類圖如下:
9.4 一般化實現
9.4.1 創建一般員工
package com.demo.common; /** * Created by lsq on 2018/3/18. * 普通員工 */ public class Employees { //員工號 private String no; //姓名 private String name; //職位 private String position; //薪資 private float salary; //構造方法 public Employees(String no, String name, String position, float salary){ this.no = no; this.name = name; this.position = position; this.salary = salary; } //獲得用戶基本信息 public void printUserBaseInfo(){ System.out.println("|"+this.no+" "+this.name+" "+this.position+" "+this.salary); } public String getNo() { return no; } public void setNo(String no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPosition() { return position; } public void setPosition(String position) { this.position = position; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } }
9.4.2 創建管理者
該類中,除了包括基本屬性內容,還包括一個ArrayList類型數組,用來存儲手下的管理者和普通員工,另有兩個增加員工的方法,一個是增加管理者的方法,另一個是增加普通員工的方法。
package com.demo.common; import java.util.ArrayList; /** * Created by lsq on 2018/3/18. * 管理者 */ public class Manager { //員工號 private String no; //姓名 private String name; //職位 private String position; //薪資 private float salary; //存儲手下員工信息 private ArrayList arrayList = new ArrayList(); //私有屬性,長度字符串 private int length; //構造方法 public Manager(String no, String name, String position, float salary){ this.no = no; this.name = name; this.position = position; this.salary = salary; //計算總字節長度 this.length += (no==null || "".equals(no.trim())) ? 0 : no.getBytes().length; this.length += (name==null || "".equals(name.trim())) ? 0 : name.getBytes().length; this.length += (position==null || "".equals(position.trim())) ? 0 : position.getBytes().length; this.length += String.valueOf(salary).getBytes().length; } //增加一個管理人員 public void add(Manager manager){ this.arrayList.add(manager); } //增加一個普通員工 public void add(Employees employees){ this.arrayList.add(employees); } //獲得用戶基本信息 public void printUserBaseInfo(){ System.out.println("|"+this.no+" "+this.name+" "+this.position+" "+this.salary); } //打印員工信息 public void printEmployeesInfo(int layer){ int tmpLayer = ++layer; for (int i=0;i<this.arrayList.size();i++){ Object object = this.arrayList.get(i); if (null == object){ continue; } //判斷是普通員工還是管理人員 if (object instanceof Employees){ //如果是普通員工,則輸出員工基本信息 //打印‘-’ printChar(tmpLayer); //打印員工基本信息 ((Employees)object).printUserBaseInfo(); }else if (object instanceof Manager){ //如果是管理人員,則輸出管理者基本信息然后輸出管理者手下員工信息 //打印‘-’ printChar(tmpLayer); //實例化Manager Manager manager = (Manager)object; //打印管理者基本信息 manager.printUserBaseInfo(); //打印管理者手下員工信息 manager.printEmployeesInfo(tmpLayer); } } } //打印若干字符 protected void printChar(int layer){ for (int j=0;j<layer * 2;j++){ System.out.print("-"); } } public String getNo() { return no; } public void setNo(String no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPosition() { return position; } public void setPosition(String position) { this.position = position; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } public ArrayList getArrayList() { return arrayList; } public void setArrayList(ArrayList arrayList) { this.arrayList = arrayList; } }
9.4.3 客戶端測試
import com.demo.common.Employees; import com.demo.common.Manager; /** * Created by lsq on 2018/3/19. * 應用程序 */ public class ClientOfCommon { public static void main(String[] args) { //公司CEO Manager boss = new Manager("1", "大老板", "CEO", 100000); /** * CEO手下有若干部門經理 */ //財務部經理 Manager financeManager = new Manager("11", "張總", "財務部經理", 60000); //人事部經理 Manager personnelManager = new Manager("12", "王總", "人事部經理", 60000); //技術部經理 Manager technicalManager = new Manager("13", "陳總", "技術部經理", 60000); /** * 技術部門還有助理和若干主管 */ //技術部門助理 Manager deptAssistant = new Manager("1301", "王助理", "部門助理", 20000); //技術部門主管1 Manager deptManager1 = new Manager("1302", "主管 1", "技術主管", 30000); /** * 技術主管deptManager1 下面還有軟件工程師 */ Employees softwareEngineer1 = new Employees("1302001", "張三", "軟件工程師", 5000); Employees softwareEngineer2 = new Employees("1302002", "李四", "軟件工程師", 5500); Employees softwareEngineer3 = new Employees("1302003", "王五", "軟件工程師", 4500); //為技術主管1添加員工 deptManager1.add(softwareEngineer1); deptManager1.add(softwareEngineer2); deptManager1.add(softwareEngineer3); //技術部門主管2 Manager deptManager2 = new Manager("1303", "主管 2", "技術主管", 30000); //為技術部門經理添加部門助理、技術主管1和技術主管2 technicalManager.add(deptAssistant); technicalManager.add(deptManager1); technicalManager.add(deptManager2); //市場部經理 Manager marketingManager = new Manager("14", "吳總", "市場部經理", 60000); //為CEO添加財務部經理、人事部經理、技術部經理和市場部經理 boss.add(financeManager); boss.add(personnelManager); boss.add(technicalManager); boss.add(marketingManager); //打印CEO信息 boss.printUserBaseInfo(); //打印CEO手下員工信息 boss.printEmployeesInfo(1); } }
運行結果:
9.4.4 系統結構的思考
這個結果滿足了我們之前的要求,但是,仔細想想,我們就會發現有問題:首先,我們在一開始就對管理者和普通員工加以區分,使我們不能統一控制;其次,在管理者打印手下員工信息時,我們還要判斷數組中的實例類型,根據不同類型作出不同的處理,這樣也增加了程序的復雜度。
那么應該如何實現,才是最好的解決方式?對於這種“整體-部分”的層級結構,最好的解決方式就是使用組合設計模式!
9.5 組合模式分析方法
我們先來分析一下,從上面的應用程序中可以看到,無論管理者還是普通員工都包含一些共同的內容,如員工的基本信息、打印員工基本信息等內容,這樣我們可以將其抽象成父類,管理者和普通員工都繼承該父類即可。那么,管理者中的增加員工信息該如何處理呢?可能你會想把增加員工信息的功能放到管理者類中,這可不是什么好想法。原因很簡單,那樣的話,管理者和普通員工又被區別對待了,不好統一管理。我們可以這樣實現,就是將增加員工信息方法作為抽象方法,放到抽象父類中,由子類負責實現。作為普通員工,沒有下屬,抽象方法就是一個空方法。這樣我們就可以統一對待每一個員工了,無論是管理者還是普通員工!
優化后的組織結構如圖所示:
9.6 公司結構的組合模式實現
9.6.1 建立員工抽象
package com.demo.composite; /** * Created by lsq on 2018/3/19. * 職工類接口 */ public abstract class Staff { //員工號 private String no; //姓名 private String name; //職位 private String position; //薪資 private float salary; //私有屬性,長度字符串 private int length; //構造方法 public Staff(String no, String name, String position, float salary) { this.no = no; this.name = name; this.position = position; this.salary = salary; //計算總字節長度 this.length += (no==null || "".equals(no.trim())) ? 0 : no.getBytes().length; this.length += (name==null || "".equals(name.trim())) ? 0 : name.getBytes().length; this.length += (position==null || "".equals(position.trim())) ? 0 : position.getBytes().length; this.length += String.valueOf(salary).getBytes().length; } //獲得用戶基本信息 public void printUserBaseInfo(){ System.out.println("|"+this.no+" "+this.name+" "+this.position+" "+this.salary); } //添加員工信息 public abstract void add(Staff staff); //刪除員工 public abstract Staff remove(String no); //打印員工信息 public abstract void printEmployeesInfo(int layer); //打印若干字符 protected void printChar(int layer){ for (int j=0;j<layer * 2;j++){ System.out.print("-"); } } //私有方法打印一行 protected void printLine(){ System.out.print("+"); for (int i=0;i<this.length + 4;i++){ System.out.print("-"); } System.out.println("-"); } public String getNo() { return no; } public void setNo(String no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPosition() { return position; } public void setPosition(String position) { this.position = position; } public float getSalary() { return salary; } public void setSalary(float salary) { this.salary = salary; } }
注意:在Staff類中還包含三個抽象方法:添加員工、刪除員工和打印員工信息。這三個抽象方法由子類負責實現。在添加員工信息的方法中,是以Staff員工父類作為參數的,這樣就可以添加任意員工的信息了,包括管理者和普通員工。
9.6.2 創建管理者
package com.demo.composite.sub; import com.demo.composite.Staff; import java.util.ArrayList; /** * Created by lsq on 2018/3/19. * 管理人員 */ public class Manager extends Staff{ //存儲手下員工信息 private final ArrayList<Staff> arrayList = new ArrayList<>(); //構造方法 public Manager(String no, String name, String position, float salary) { super(no, name, position, salary); } /** * 增加一個員工 * @param staff */ @Override public void add(Staff staff) { this.arrayList.add(staff); } /** * 刪除員工信息 * @param no * @return */ @Override public Staff remove(String no) { Staff staff = null; if (no!=null && !"".equals(no.trim())){ for (int i=0;i<arrayList.size();i++){ if (this.arrayList.get(i) == null){ continue; } if (no.equals(this.arrayList.get(i).getNo())){ staff = this.arrayList.remove(i); break; } } } return staff; } /** * 打印員工信息 * @param layer */ @Override public void printEmployeesInfo(int layer) { int tmpLayer = ++layer; for (int i=0;i<this.arrayList.size();i++){ if (this.arrayList.get(i) == null){ continue; } //打印'-' printChar(tmpLayer); //打印員工基本信息 this.arrayList.get(i).printUserBaseInfo(); //打印手下員工信息 this.arrayList.get(i).printEmployeesInfo(tmpLayer); } } }
注:管理者類的內容主要是實現了父類的三個方法,添加員工信息使用的是Staff基類類型,這樣就可以統一管理對象。在打印員工信息方法中,我們遍歷了list中所有的Staff對象,但是我們並沒有判斷是管理者還是普通員工,只是統一調用。
9.6.3 創建普通員工
package com.demo.composite.sub; import com.demo.composite.Staff; /** * Created by lsq on 2018/3/19. * 普通員工 */ public class Employees extends Staff{ //構造方法 public Employees(String no, String name, String position, float salary) { super(no, name, position, salary); } /** * 添加員工信息 * @param staff */ @Override public void add(Staff staff) { return; } /** * 刪除員工信息 * @param no * @return */ @Override public Staff remove(String no) { return null; } //打印員工信息 @Override public void printEmployeesInfo(int layer) { return; } }
9.6.4 客戶端測試
import com.demo.composite.Staff; import com.demo.composite.sub.Employees; import com.demo.composite.sub.Manager; /** * Created by lsq on 2018/3/19. * 應用程序 */ public class Client { public static void main(String[] args) { //公司CEO Staff boss = new Manager("1", "大老板", "CEO", 100000); /** * CEO手下有若干部門經理 */ //財務部經理 Staff financeManager = new Manager("11", "張總", "財務部經理", 60000); //人事部經理 Staff personnelManager = new Manager("12", "王總", "人事部經理", 60000); //技術部經理 Staff technicalManager = new Manager("13", "陳總", "技術部經理", 60000); /** * 技術部門還有助理和若干主管 */ //技術部門助理 Staff deptAssistant = new Manager("1301", "王助理", "部門助理", 20000); //技術部門主管1 Staff deptManager1 = new Manager("1302", "主管 1", "技術主管", 30000); /** * 技術主管deptManager1 下面還有軟件工程師 */ Staff softwareEngineer1 = new Employees("1302001", "張三", "軟件工程師", 5000); Staff softwareEngineer2 = new Employees("1302002", "李四", "軟件工程師", 5500); Staff softwareEngineer3 = new Employees("1302003", "王五", "軟件工程師", 4500); //為技術主管1添加員工 deptManager1.add(softwareEngineer1); deptManager1.add(softwareEngineer2); deptManager1.add(softwareEngineer3); //技術部門主管2 Staff deptManager2 = new Manager("1303", "主管 2", "技術主管", 30000); //為技術部門經理添加部門助理、技術主管1和技術主管2 technicalManager.add(deptAssistant); technicalManager.add(deptManager1); technicalManager.add(deptManager2); //市場部經理 Staff marketingManager = new Manager("14", "吳總", "市場部經理", 60000); //為CEO添加財務部經理、人事部經理、技術部經理和市場部經理 boss.add(financeManager); boss.add(personnelManager); boss.add(technicalManager); boss.add(marketingManager); //打印CEO信息 boss.printUserBaseInfo(); //打印CEO手下員工信息 boss.printEmployeesInfo(1); } }
運行結果:
9.7 使用場合
1)想表示對象的“部分-整體”層次結構的時候;
2)希望用戶忽略組合對象與單個對象的不同,用戶將統一使用組合結構中的所有對象的時候。