迭代器模式
遍历菜单
好消息,楼下的餐厅和早餐店合并了,现在我们可以在同一个地方享受早餐和午餐了。但在这个过程中,老板遇到了一些麻烦,需要你帮他解决。
麻烦是,餐厅菜单类使用的是基于数组的菜单项,而早餐店菜单类使用的是基于列表的菜单项,因为这两个类在他们原来各自的系统中都是基础类,被依赖使用的地方很多,可不能简单粗暴的进行整合修改。
如果保留两种不同的菜单表现形式,会带来什么问题?
我们可以新创建一个使用这两个菜单类的客户,即女招待来看看,女招待的规格大概为:打印所有菜单项,只打印早餐项,只打印午餐项,打印所有素食菜单项等。
首先,菜单项是大家都统一的,源码如下:
/**
* 菜单项
*/
public class MenuItem {
private String name; //菜单名称
private String desc; //菜单描述
private boolean vegetarian; //素食标识
private double price; //价格
public MenuItem(String name, String desc, boolean vegetarian, double price) {
this.name = name;
this.desc = desc;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public boolean isVegetarian() {
return vegetarian;
}
public double getPrice() {
return price;
}
}
V1版
早餐菜单,通过ArrayList来放菜单项:
/**
* 煎饼屋菜单
*/
public class PancakeHouseMenuV1 {
private ArrayList<MenuItem> menuItems;
public PancakeHouseMenuV1() {
menuItems = new ArrayList<>();
addItem("I型煎饼", "I型煎饼是纯煎饼,不加任何东西", true, 2.0d);
addItem("II型煎饼", "II型煎饼,加一个鸡蛋", false, 3.0d);
addItem("III型煎饼", "III型煎饼,加里脊肉,好吃", false, 4.0d);
addItem("IIII型煎饼", "IIII型煎饼,又加鸡蛋又加里脊肉,再来点辣酱,爽", false, 5.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
menuItems.add(menuItem);
}
public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}
}
午餐菜单,通过数组来放菜单项:
/**
* 午餐菜单
*/
public class DinerMenuV1 {
private static final int MAX_ITEMS = 6;
private int numberOfItems = 0;
private MenuItem[] menuItems;
public DinerMenuV1() {
menuItems = new MenuItem[6];
addItem("红烧茄子套餐", "红烧茄子,好词又营养,还不贵", true, 12.0d);
addItem("麻婆豆腐套餐", "麻婆豆腐,麻辣鲜香", true, 13.0d);
addItem("酸菜鱼套餐", "可以喝汤的酸菜鱼,又酸又鲜还无刺,你值得拥有", false, 24.0d);
addItem("红烧牛肉套餐", "选用定级牛肉,加入各种调料炖煮8个小时,入口即化,好吃到爆", false, 35.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
if(numberOfItems >= MAX_ITEMS){
System.err.println("菜单已满,不能再往里添加菜单项了..");
}else{
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
}
现在我们来实现下女招待:
/**
* 服务员
*/
public class WaitressV1 {
private PancakeHouseMenuV1 pancakeHouseMenu;
private DinerMenuV1 dinerMenu;
public WaitressV1(PancakeHouseMenuV1 pancakeHouseMenu, DinerMenuV1 dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
//报全部菜单项
public void printMenu(){
System.out.println("早餐菜单:");
ArrayList<MenuItem> pancakeHouseMenuItems = pancakeHouseMenu.getMenuItems();
for (int i = 0; i < pancakeHouseMenuItems.size(); i++) {
MenuItem menuItem = pancakeHouseMenuItems.get(i);
System.out.println(menuItem.getName() + "\t" + menuItem.getPrice() + "\t" + menuItem.getDesc());
}
System.out.println("午餐菜单:");
MenuItem[] dinerMenuItems = dinerMenu.getMenuItems();
for (int i = 0; i < dinerMenuItems.length; i++) {
MenuItem menuItem = dinerMenuItems[i];
if(null == menuItem){
continue;
}
System.out.println(menuItem.getName() + "\t" + menuItem.getPrice() + "\t" + menuItem.getDesc());
}
}
//只报早餐菜单
public void printBreakfastMenu(){}
//只报午餐菜单
public void printLunchMenu(){}
//只报素食菜单
public void printVegetarianMenu(){}
//指定的菜单项的名称,判断是否为素食
public void isVegetarian(String name){}
}
我们的女招待持有早餐菜单和午餐菜单,在printMenu()方法中,分别获取早餐和午餐菜单的具体菜单项的数据结构,然后遍历。
其他方法同理,未实现,你可以试着实现一下。
最后测试一波:
/**
* 测试
*/
public class MenuMainV1 {
public static void main(String[] args) {
PancakeHouseMenuV1 pancakeHouseMenu = new PancakeHouseMenuV1();
DinerMenuV1 dinerMenu = new DinerMenuV1();
WaitressV1 waitress = new WaitressV1(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
V1版本的实现,我们发现女招待需要知道每个菜单的内部中用来盛放菜单项数据结构,耦合具体的数据结构,比如在扩展一个新的菜单,其内部使用HashMap来放菜单项,那么我们的女招待要修改以支持新的数据结构。
同时,女招待也是耦合具体的菜单,是针对具体编程,而不是针对接口。
对于针对接口编程这个问题,我们可以抽象出菜单接口解决,但如何来解决女招待依赖具体的数据结构呢?观察下女招待的代码,不难发现,其中变化的部分对不同的数据结构的类型做遍历。
那么我们能够封装遍历吗?试试看。
V2版
抽象出一个MenuIteratorV2接口,来封装"遍历数据结构中每个对象"。
/**
* 菜单迭代器
*/
public interface MenuIteratorV2 {
boolean hasNext(); //是否有下一个
MenuItem next(); //取下一个
}
午餐菜单的迭代器实现,它可以遍历数组的数据结构:
/**
* 午餐菜单迭代器实现
*/
public class DinerMenuIteratorV2 implements MenuIteratorV2 {
private MenuItem[] menuItems;
private int position = 0;
public DinerMenuIteratorV2(MenuItem[] menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if(position > menuItems.length || menuItems[position] == null){
return false;
}else {
return true;
}
}
@Override
public MenuItem next() {
MenuItem menuItem = menuItems[position];
position++;
return menuItem;
}
}
早餐菜单的迭代器实现,它可以遍历列表数据结构:
/**
* 煎饼屋菜单迭代器实现
*/
public class PancakeHouseMenuIteratorV2 implements MenuIteratorV2 {
private ArrayList<MenuItem> menuItems;
private int position = 0;
public PancakeHouseMenuIteratorV2(ArrayList<MenuItem> menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if(position >= menuItems.size()){
return false;
}else {
return true;
}
}
@Override
public MenuItem next() {
MenuItem menuItem = menuItems.get(position);
position++;
return menuItem;
}
}
现在,让菜单类添加一个方法,返回迭代器实例
/**
* 午餐菜单
*/
public class DinerMenuV2 {
private static final int MAX_ITEMS = 6;
private int numberOfItems = 0;
private MenuItem[] menuItems;
public DinerMenuV2() {
menuItems = new MenuItem[6];
addItem("红烧茄子套餐", "红烧茄子,好词又营养,还不贵", true, 12.0d);
addItem("麻婆豆腐套餐", "麻婆豆腐,麻辣鲜香", true, 13.0d);
addItem("酸菜鱼套餐", "可以喝汤的酸菜鱼,又酸又鲜还无刺,你值得拥有", false, 24.0d);
addItem("红烧牛肉套餐", "选用定级牛肉,加入各种调料炖煮8个小时,入口即化,好吃到爆", false, 35.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
if(numberOfItems >= MAX_ITEMS){
System.err.println("菜单已满,不能再往里添加菜单项了..");
}else{
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
public MenuIteratorV2 createIterator(){
return new DinerMenuIteratorV2(menuItems);
}
}
/**
* 煎饼屋菜单
*/
public class PancakeHouseMenuV2 {
private ArrayList<MenuItem> menuItems;
public PancakeHouseMenuV2() {
menuItems = new ArrayList<>();
addItem("I型煎饼", "I型煎饼是纯煎饼,不加任何东西", true, 2.0d);
addItem("II型煎饼", "II型煎饼,加一个鸡蛋", false, 3.0d);
addItem("III型煎饼", "III型煎饼,加里脊肉,好吃", false, 4.0d);
addItem("IIII型煎饼", "IIII型煎饼,又加鸡蛋又加里脊肉,再来点辣酱,爽", false, 5.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
menuItems.add(menuItem);
}
public MenuIteratorV2 createIterator(){
return new PancakeHouseMenuIteratorV2(menuItems);
}
}
此时我们的女招待就相对来说轻松很多,不用关心具体的数据结构了
/**
* 服务员
*/
public class WaitressV2 {
private PancakeHouseMenuV2 pancakeHouseMenu;
private DinerMenuV2 dinerMenu;
public WaitressV2(PancakeHouseMenuV2 pancakeHouseMenu, DinerMenuV2 dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
//报全部菜单
public void printMenu(){
System.out.println("早餐菜单:");
MenuIteratorV2 pancakeHouseMenuIterator = pancakeHouseMenu.createIterator();
printMenu(pancakeHouseMenuIterator);
System.out.println("午餐菜单:");
MenuIteratorV2 dinerMenuIterator = dinerMenu.createIterator();
printMenu(dinerMenuIterator);
}
private void printMenu(MenuIteratorV2 menuIterator) {
while(menuIterator.hasNext()){
MenuItem menuItem = menuIterator.next();
System.out.println(menuItem.getName() + "\t" + menuItem.getPrice() + "\t" + menuItem.getDesc());
}
}
//只报早餐菜单
public void printBreakfastMenu(){
}
//只报午餐菜单
public void printLunchMenu(){}
//只报素食菜单
public void printVegetarianMenu(){}
//指定的菜单项的名称,判断是否为素食
public void isVegetarian(String name){}
}
最后,测试代码
public class MenuMainV2 {
public static void main(String[] args) {
PancakeHouseMenuV2 pancakeHouseMenu = new PancakeHouseMenuV2();
DinerMenuV2 dinerMenu = new DinerMenuV2();
WaitressV2 waitress = new WaitressV2(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
V2版本的UML
嗯,V2版本解决了女招待依赖具体数据结构的问题,但是没有解决耦合具体菜单的问题。
前面我们说,可以抽象出统一的菜单接口来解决,实际上对于我们的需求来说,女招待只需要一个createIterator()方法接口即可,
另外,我们的MenuIteratorV2类的定义也是标准的,且在JDK中已经定义好了,他们就是Iterator和Iterable这两个接口。
接下来,我们使用这两个接口来优化一波
V3版
首先,早餐菜单中用到的数据结构是ArrayList,且ArrayList已经提供了其迭代器实现,因此可以直接范围ArrayList的迭代器
早餐菜单实现Iterable接口。
public class PancakeHouseMenuV3 implements Iterable<MenuItem>{
private ArrayList<MenuItem> menuItems;
public PancakeHouseMenuV3() {
menuItems = new ArrayList<>();
addItem("I型煎饼", "I型煎饼是纯煎饼,不加任何东西", true, 2.0d);
addItem("II型煎饼", "II型煎饼,加一个鸡蛋", false, 3.0d);
addItem("III型煎饼", "III型煎饼,加里脊肉,好吃", false, 4.0d);
addItem("IIII型煎饼", "IIII型煎饼,又加鸡蛋又加里脊肉,再来点辣酱,爽", false, 5.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
menuItems.add(menuItem);
}
@Override
public Iterator<MenuItem> iterator() {
return menuItems.iterator();
}
}
数组没有提供迭代器实现,我们需要单独实现一个
/**
* 午餐菜单迭代器实现
*/
public class DinerMenuIteratorV3 implements Iterator<MenuItem> {
private MenuItem[] menuItems;
private int position = 0;
public DinerMenuIteratorV3(MenuItem[] menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if(position > menuItems.length || menuItems[position] == null){
return false;
}else {
return true;
}
}
@Override
public MenuItem next() {
MenuItem menuItem = menuItems[position];
position++;
return menuItem;
}
}
接下来,午餐菜单实现Iterable接口
public class DinerMenuV3 implements Iterable<MenuItem>{
private static final int MAX_ITEMS = 6;
private int numberOfItems = 0;
private MenuItem[] menuItems;
public DinerMenuV3() {
menuItems = new MenuItem[6];
addItem("红烧茄子套餐", "红烧茄子,好词又营养,还不贵", true, 12.0d);
addItem("麻婆豆腐套餐", "麻婆豆腐,麻辣鲜香", true, 13.0d);
addItem("酸菜鱼套餐", "可以喝汤的酸菜鱼,又酸又鲜还无刺,你值得拥有", false, 24.0d);
addItem("红烧牛肉套餐", "选用定级牛肉,加入各种调料炖煮8个小时,入口即化,好吃到爆", false, 35.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
if(numberOfItems >= MAX_ITEMS){
System.err.println("菜单已满,不能再往里添加菜单项了..");
}else{
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
@Override
public Iterator<MenuItem> iterator() {
return new DinerMenuIteratorV3(menuItems);
}
}
此时,女招待只需要依赖Iterator即可
public class WaitressV3 {
private Iterable<MenuItem> pancakeHouseMenu;
private Iterable<MenuItem> dinerMenu;
public WaitressV3(PancakeHouseMenuV3 pancakeHouseMenu, DinerMenuV3 dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
//报全部菜单
public void printMenu(){
System.out.println("早餐菜单:");
Iterator<MenuItem> pancakeHouseMenuIterator = pancakeHouseMenu.iterator();
printMenu(pancakeHouseMenuIterator);
System.out.println("午餐菜单:");
Iterator<MenuItem> dinerMenuIterator = dinerMenu.iterator();
printMenu(dinerMenuIterator);
}
private void printMenu(Iterator<MenuItem> menuItemIterator) {
while(menuItemIterator.hasNext()){
MenuItem menuItem = menuItemIterator.next();
System.out.println(menuItem.getName() + "\t" + menuItem.getPrice() + "\t" + menuItem.getDesc());
}
}
//只报早餐菜单
public void printBreakfastMenu(){
}
//只报午餐菜单
public void printLunchMenu(){}
//只报素食菜单
public void printVegetarianMenu(){}
//指定的菜单项的名称,判断是否为素食
public void isVegetarian(String name){}
}
测试:
public class MenuMainV3 {
public static void main(String[] args) {
PancakeHouseMenuV3 pancakeHouseMenu = new PancakeHouseMenuV3();
DinerMenuV3 dinerMenu = new DinerMenuV3();
WaitressV3 waitress = new WaitressV3(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
}
}
V3版本的UML
V3版本中,女招待的实现限定了只能通过构造方法传入菜单实例,我们可以使用集合来装菜单,让女招待更具有扩展性。
V4版
我们可以加入一个新的咖啡菜单,其内部使用haspMap来保存菜单项
public class CafeMenuV4 implements Iterable<MenuItem> {
private HashMap<String, MenuItem> menuItemMap = new HashMap<>();
public CafeMenuV4() {
addItem("简单的热咖啡", "原味咖啡,带感", true, 5.0d);
addItem("加奶热咖啡", "热咖啡,家电奶,口感不错", false, 7.0d);
addItem("加糖冰咖啡", "冰咖啡,可能放点糖要好点", true, 8.0d);
}
private void addItem(String name, String desc, boolean vegetarian, double price) {
addItem(new MenuItem(name, desc, vegetarian, price));
}
private void addItem(MenuItem menuItem) {
menuItemMap.put(menuItem.getName(), menuItem);
}
@Override
public Iterator<MenuItem> iterator() {
return menuItemMap.values().iterator();
}
}
更加灵活的女招待。
public class WaitressV4 {
private List<Iterable<MenuItem>> iterables;
public WaitressV4() {
this.iterables = new ArrayList<>();
}
public void addMenu(Iterable<MenuItem> iterable){
iterables.add(iterable);
}
//报全部菜单
public void printMenu(){
System.out.println("菜单:");
for (Iterable<MenuItem> iterable : iterables) {
printMenu(iterable.iterator());
System.out.println();
}
}
private void printMenu(Iterator<MenuItem> menuItemIterator) {
while(menuItemIterator.hasNext()){
MenuItem menuItem = menuItemIterator.next();
System.out.println(menuItem.getName() + "\t" + menuItem.getPrice() + "\t" + menuItem.getDesc());
}
}
//只报早餐菜单
public void printBreakfastMenu(){
}
//只报午餐菜单
public void printLunchMenu(){}
//只报素食菜单
public void printVegetarianMenu(){}
//指定的菜单项的名称,判断是否为素食
public void isVegetarian(String name){}
}
测试:
public class MenuMainV4 {
public static void main(String[] args) {
PancakeHouseMenuV4 pancakeHouseMenu = new PancakeHouseMenuV4();
DinerMenuV4 dinerMenu = new DinerMenuV4();
CafeMenuV4 cafeMenu = new CafeMenuV4();
WaitressV4 waitress = new WaitressV4();
waitress.addMenu(pancakeHouseMenu);
waitress.addMenu(dinerMenu);
waitress.addMenu(cafeMenu);
waitress.printMenu();
}
}
定义
迭代器模式提供了一种方法来遍历访问聚合对象中的各个元素,而又不暴露其内部表示(内部数据结构)。
用代码表示:
迭代器抽象
/**
* 迭代器接口
*/
public interface Iter {
boolean hasNext();
Object next();
}
具体的迭代器实现
/**
* 具体迭代器实现
* 它内部会封装不同的数据结构
*/
public class ConcreteIter implements Iter {
private List list;
private Iterator insideIter; //简单起见,使用List自生的迭代器实现。
public ConcreteIter(List list) {
this.list = list;
insideIter = this.list.iterator();
}
@Override
public boolean hasNext() {
return insideIter.hasNext();
}
@Override
public Object next() {
return insideIter.next();
}
}
聚集对象的抽象
/**
* 抽象的聚集对象
*/
public interface Aggregate {
Iter createIter(); //获取迭代器对象
}
聚集对象的实现
/**
* 具体的聚集对象
*/
public class ConcreteAggregate implements Aggregate{
private List list = new ArrayList(); //简单起见,其内部使用list。
public ConcreteAggregate() {
for (int i = 0; i < 10; i++) {
list.add("element" + i);
}
}
@Override
public Iter createIter() {
return new ConcreteIter(list);
}
}
客户
/**
* 客户
*/
public class Client {
private Aggregate aggregate;
public Client(Aggregate aggregate) {
this.aggregate = aggregate;
}
public void traverse(){
Iter iter = aggregate.createIter();
while (iter.hasNext()){
System.out.println(iter.next().toString());
}
}
public static void main(String[] args) {
new Client(new ConcreteAggregate()).traverse();
}
}
迭代器模式的UML图
扩展示例
该示例来源 https://www.journaldev.com/1716/iterator-design-pattern-java
假设我们有一个无线电频道的集合,客户端程序希望能够通过频道类型,来一个一个的遍历频道,比如有的客户端只想遍历英语类型的频道,而有的只想遍历中文频道。
当然我们可以提供一个频道集合给到客户端程序,让他们去写遍历的逻辑,甚至于过滤类型,但是这样不大有好。这个需求我们可以使用迭代器模式,且迭代器是基于频道类型的。
首先,将频道类型和频道类创建出来,准备工作做好
/**
* 频道类型
*/
public enum ChannelTypeEnum {
ENGLISH, HINDI, FRENCH, ALL;
}
/**
* 无线电频道
*/
public class Channel {
private double frequency;
private ChannelTypeEnum TYPE;
public Channel(double freq, ChannelTypeEnum type){
this.frequency=freq;
this.TYPE=type;
}
public double getFrequency() {
return frequency;
}
public ChannelTypeEnum getTYPE() {
return TYPE;
}
@Override
public String toString(){
return "Frequency="+this.frequency+", Type="+this.TYPE;
}
}
然后,定义频道集合的接口
/**
* 频道集合接口
*/
public interface ChannelCollection {
public void addChannel(Channel c);
public void removeChannel(Channel c);
public ChannelIterator iterator(ChannelTypeEnum type);
}
该接口没有提供方法返回频道集合,而是定义了返回频道迭代器的方法。
频道迭代器接口如下:
/**
* 频道迭代器接口
*/
public interface ChannelIterator {
public boolean hasNext();
public Channel next();
}
最后,实现具体的频道集合和迭代器
/**
* 频道集合实现
* 迭代器的实现使用内部类
*/
public class ChannelCollectionImpl implements ChannelCollection {
private List<Channel> channelsList;
public ChannelCollectionImpl() {
channelsList = new ArrayList<>();
}
public void addChannel(Channel c) {
this.channelsList.add(c);
}
public void removeChannel(Channel c) {
this.channelsList.remove(c);
}
@Override
public ChannelIterator iterator(ChannelTypeEnum type) {
return new ChannelIteratorImpl(type, this.channelsList);
}
//私有内部类,仅自己使用。
private class ChannelIteratorImpl implements ChannelIterator {
private ChannelTypeEnum type;
private List<Channel> channels;
private int position;
public ChannelIteratorImpl(ChannelTypeEnum ty,
List<Channel> channelsList) {
this.type = ty;
this.channels = channelsList;
}
@Override
public boolean hasNext() {
while (position < channels.size()) {
Channel c = channels.get(position);
if (c.getTYPE().equals(type) || type.equals(ChannelTypeEnum.ALL)) {
return true;
} else
position++;
}
return false;
}
@Override
public Channel next() {
Channel c = channels.get(position);
position++;
return c;
}
}
}
客户使用
/**
* 测试
*/
public class IteratorPatternTest {
public static void main(String[] args) {
ChannelCollection channels = populateChannels();
ChannelIterator baseIterator = channels.iterator(ChannelTypeEnum.ALL);
while (baseIterator.hasNext()) {
Channel c = baseIterator.next();
System.out.println(c.toString());
}
System.out.println("******");
// Channel Type Iterator
ChannelIterator englishIterator = channels.iterator(ChannelTypeEnum.ENGLISH);
while (englishIterator.hasNext()) {
Channel c = englishIterator.next();
System.out.println(c.toString());
}
}
private static ChannelCollection populateChannels() {
ChannelCollection channels = new ChannelCollectionImpl();
channels.addChannel(new Channel(98.5, ChannelTypeEnum.ENGLISH));
channels.addChannel(new Channel(99.5, ChannelTypeEnum.HINDI));
channels.addChannel(new Channel(100.5, ChannelTypeEnum.FRENCH));
channels.addChannel(new Channel(101.5, ChannelTypeEnum.ENGLISH));
channels.addChannel(new Channel(102.5, ChannelTypeEnum.HINDI));
channels.addChannel(new Channel(103.5, ChannelTypeEnum.FRENCH));
channels.addChannel(new Channel(104.5, ChannelTypeEnum.ENGLISH));
channels.addChannel(new Channel(105.5, ChannelTypeEnum.HINDI));
channels.addChannel(new Channel(106.5, ChannelTypeEnum.FRENCH));
return channels;
}
}
组合模式
在菜单V4版本中,我们的菜单使用迭代器模式,对客户屏蔽了底层的具体数据结构,封装了对聚合对象的遍历。
现在,餐厅老板希望加上一份餐后甜点的"子菜单",即午餐菜单中包含菜单项和子菜单(甜点菜单),子菜单再包含菜单项,形成树形结构。
对于这种将对象组织成树形结构,表现整体/部分层次结构的使用,可使用组合模式实现。
定义
先来看看组合模式的定义,它的正是定义如下:
组合模式允许你将对象组织成树型结构来表示"部分/整体"的层次结构,组合能让客户已一致的方式对待单个以及对象组合。
废话少说,使用代码解释一下:
抽象的组合接口,它是单个对象和组合对象的共同接口,客户只依赖该接口即可。
/**
* 组合接口
* 是叶子节点和组合节点的共同接口。
*/
public interface Component {
void operation(); //
void add(Component component);
void remove(Component component);
}
叶子节点的实现:
/**
* 叶子节点
*/
public class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("在"+ name +"叶子上的操作...");
}
@Override
public void add(Component component) {
//do nothing
}
@Override
public void remove(Component component) {
//do nothing
}
}
组合节点的实现:
/**
* 非叶子节点, 对象组合
*/
public class Composite implements Component {
private List<Component> childs;
private String name;
public Composite(String name) {
this.name = name;
this.childs = new ArrayList<>();
}
@Override
public void operation() {
System.out.println("-----在组合节点"+name+"上的操作------");
childs.forEach(child -> child.operation());
}
@Override
public void add(Component component) {
childs.add(component);
}
@Override
public void remove(Component component) {
childs.remove(component);
}
}
这儿组合节点上的操作是遍历了其孩子节点,并调用每个孩子节点的操作来实现的,这儿实现了递归效果,实际情况中,需要根据需求实现组合节点的操作。
最后,看下怎么使用的:
/**
* 客户
*/
public class Client {
public static void main(String[] args) {
Component root = new Composite("根节点");
Component compositeA = new Composite("组合节点A");
Component compositeB = new Composite("组合节点B");
compositeA.add(new Leaf("LeafA-1"));
compositeA.add(new Leaf("LeafA-2"));
compositeB.add(new Leaf("LeafB-1"));
compositeB.add(new Leaf("LeafB-2"));
root.add(compositeA);
root.add(compositeB);
root.operation();
}
}
输出结果
-----在组合节点根节点上的操作------
-----在组合节点组合节点A上的操作------
在LeafA-1叶子上的操作...
在LeafA-2叶子上的操作...
-----在组合节点组合节点B上的操作------
在LeafB-1叶子上的操作...
在LeafB-2叶子上的操作...
看看其UML图:
回到菜单
我们使用组合模式来解决子菜单的需求。
V5版
首先,我们需要抽象出叶子节点和组合节点的共同父类,菜单组件类
/**
* 菜单组件接口
*/
public abstract class MenuComponentV5 {
public void add(MenuComponentV5 menuComponent){
throw new UnsupportedOperationException();
}
public void remove(MenuComponentV5 menuComponent){
throw new UnsupportedOperationException();
}
public MenuComponentV5 getChild(int i){
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
}
public String getDesc() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
}
注意,这次我们没有将它定义为接口,而是定义为抽象类,这样我们提供了方法的默认实现,便于叶子节点中只需要实现操作相关方法即可。
然后,实现叶子节点,即菜单项:
/**
* 菜单项
* 叶子
*/
public class MenuItemV5 extends MenuComponentV5 {
private String name; //菜单名称
private String desc; //菜单描述
private boolean vegetarian; //素食标识
private double price; //价格
public MenuItemV5(String name, String desc, boolean vegetarian, double price) {
this.name = name;
this.desc = desc;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public boolean isVegetarian() {
return vegetarian;
}
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.print(" " + getName());
if(isVegetarian()){
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDesc());
}
}
最后,实现组合节点,即菜单组合类
/**
* 菜单组合对象
* 可包括菜单组件孩子
*/
public class MenuCompositeV5 extends MenuComponentV5 {
private List<MenuComponentV5> childs = new ArrayList<>();
private String name;
private String desc;
public MenuCompositeV5(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public void add(MenuComponentV5 menuComponent) {
childs.add(menuComponent);
}
@Override
public void remove(MenuComponentV5 menuComponent) {
childs.remove(menuComponent);
}
@Override
public MenuComponentV5 getChild(int i) {
return childs.get(i);
}
@Override
public String getName() {
return name;
}
@Override
public String getDesc() {
return desc;
}
@Override
public void print() {
System.out.print("\n" + getName());
System.out.println(", " + getDesc());
System.out.println("------------------");
for (MenuComponentV5 child : childs) {
child.print();
}
}
}
对组合对象的操作方法,调用了其孩子的操作,形成递归遍历所有孩子。
看下客户如何使用,也就是我们的女招待类
/**
* 服务员
*/
public class WaitressV5 {
private MenuComponentV5 rootMenu;
public WaitressV5(MenuComponentV5 rootMenu) {
this.rootMenu = rootMenu;
}
//报全部菜单
public void printMenu(){
System.out.println("菜单:");
rootMenu.print();
}
}
哇塞,对于女招待,不用关心叶子节点,组合节点做分别处理,统一调用根节点,它的内部会完成所有菜单的遍历。
测试一波
/**
* 测试
*/
public class MenuMainV5 {
public static void main(String[] args) {
MenuComponentV5 rootMenu = new MenuCompositeV5("所有菜单", "菜单树根");
MenuComponentV5 pancakeHouseMenu = new MenuCompositeV5("煎饼屋菜单", "早餐");
pancakeHouseMenu.add(new MenuItemV5("I型煎饼", "I型煎饼是纯煎饼,不加任何东西", true, 2.0d));
pancakeHouseMenu.add(new MenuItemV5("II型煎饼", "II型煎饼,加一个鸡蛋", false, 3.0d));
pancakeHouseMenu.add(new MenuItemV5("III型煎饼", "III型煎饼,加里脊肉,好吃", false, 4.0d));
pancakeHouseMenu.add(new MenuItemV5("IIII型煎饼", "IIII型煎饼,又加鸡蛋又加里脊肉,再来点辣酱,爽", false, 5.0d));
rootMenu.add(pancakeHouseMenu);
MenuComponentV5 dinerMenu = new MenuCompositeV5("午餐菜单", "午餐");
dinerMenu.add(new MenuItemV5("红烧茄子套餐", "红烧茄子,好词又营养,还不贵", true, 12.0d));
dinerMenu.add(new MenuItemV5("麻婆豆腐套餐", "麻婆豆腐,麻辣鲜香", true, 13.0d));
dinerMenu.add(new MenuItemV5("酸菜鱼套餐", "可以喝汤的酸菜鱼,又酸又鲜还无刺,你值得拥有", false, 24.0d));
dinerMenu.add(new MenuItemV5("红烧牛肉套餐", "选用定级牛肉,加入各种调料炖煮8个小时,入口即化,好吃到爆", false, 35.0d));
MenuComponentV5 dessertMenu = new MenuCompositeV5("甜点菜单", "甜点");
dessertMenu.add(new MenuItemV5("苹果派", "苹果派冰淇淋..", true, 3.5d));
dessertMenu.add(new MenuItemV5("蓝莓派", "蓝莓派冰淇淋..", true, 5.5d));
dinerMenu.add(dessertMenu);
rootMenu.add(dinerMenu);
MenuComponentV5 cafeMenu = new MenuCompositeV5("咖啡菜单", "晚餐");
cafeMenu.add(new MenuItemV5("简单的热咖啡", "原味咖啡,带感", true, 5.0d));
cafeMenu.add(new MenuItemV5("加奶热咖啡", "热咖啡,家电奶,口感不错", false, 7.0d));
cafeMenu.add(new MenuItemV5("加糖冰咖啡", "冰咖啡,可能放点糖要好点", true, 8.0d));
rootMenu.add(cafeMenu);
WaitressV5 waitress = new WaitressV5(rootMenu);
waitress.printMenu();
}
}
嗯,组装这个菜单树还是需要写些代码,但是遍历打印菜单只需要一行代码。
其UML图如下:
在V5之上,要让女招待提供"报素食菜单",当然可以在我们的菜单组件接口MenuComponentV5上添加"报素食菜单"方法,然后在菜单组合对象MenuCompositeV5中实现,即类似于print()方法实现。
但这样的设计会让菜单组件接口越来越臃肿,不符合单一职责原则。对于"报素食菜单"这样的需求,在MenuComponentV5外部单独封装遍历,这样我们就需要用到迭代器模式。
V6版
在V6中,我们菜单组件接口中加入创建迭代器方法:
/**
* 菜单组件接口
*/
public abstract class MenuComponentV6 {
public void add(MenuComponentV6 menuComponent){
throw new UnsupportedOperationException();
}
public void remove(MenuComponentV6 menuComponent){
throw new UnsupportedOperationException();
}
public MenuComponentV6 getChild(int i){
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
}
public String getDesc() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
public abstract Iterator<MenuComponentV6> createIterator();
}
叶子节点菜单项的实现
/**
* 菜单项
* 叶子
*/
public class MenuItemV6 extends MenuComponentV6 {
private String name; //菜单名称
private String desc; //菜单描述
private boolean vegetarian; //素食标识
private double price; //价格
public MenuItemV6(String name, String desc, boolean vegetarian, double price) {
this.name = name;
this.desc = desc;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public boolean isVegetarian() {
return vegetarian;
}
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.print(" " + getName());
if(isVegetarian()){
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDesc());
}
@Override
public Iterator<MenuComponentV6> createIterator() {
return new NullIterator();
}
}
这里使用了NullIterator,使用了空对象的思想
/**
* 空迭代器。
*/
public class NullIterator implements Iterator<MenuComponentV6> {
@Override
public boolean hasNext() {
return false;
}
@Override
public MenuComponentV6 next() {
return null;
}
}
然后是组合对象的实现
/**
* 菜单组合对象
* 可包括菜单组件孩子
*/
public class MenuCompositeV6 extends MenuComponentV6 {
private List<MenuComponentV6> childs = new ArrayList<>();
private String name;
private String desc;
public MenuCompositeV6(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public void add(MenuComponentV6 menuComponent) {
childs.add(menuComponent);
}
@Override
public void remove(MenuComponentV6 menuComponent) {
childs.remove(menuComponent);
}
@Override
public MenuComponentV6 getChild(int i) {
return childs.get(i);
}
@Override
public String getName() {
return name;
}
@Override
public String getDesc() {
return desc;
}
@Override
public void print() {
System.out.print("\n" + getName());
System.out.println(", " + getDesc());
System.out.println("------------------");
for (MenuComponentV6 child : childs) {
child.print();
}
}
@Override
public Iterator<MenuComponentV6> createIterator() {
return new CompositeIterator(childs.iterator());
}
}
创建迭代器方法返回的是自己实现的迭代器 CompositeIterator
/**
* 菜单组合对象迭代器。
*/
public class CompositeIterator implements Iterator<MenuComponentV6> {
private Stack<Iterator<MenuComponentV6>> stack;
public CompositeIterator(Iterator<MenuComponentV6> iterator) {
stack = new Stack<>();
stack.push(iterator);
}
@Override
public boolean hasNext() {
if(stack.isEmpty()){
return false;
}else {
Iterator<MenuComponentV6> iterator = stack.peek();
if(!iterator.hasNext()){
stack.pop();
return hasNext();
}else{
return true;
}
}
}
@Override
public MenuComponentV6 next() {
if(hasNext()){
Iterator<MenuComponentV6> iterator = stack.peek();
MenuComponentV6 component = iterator.next();
if(component instanceof MenuCompositeV6){
stack.push(component.createIterator());
}
return component;
}else{
return null;
}
}
}
这是我们的关键代码,在这个迭代器中,使用递归的方法来遍历所有孩子,以及孩子的孩子等。
女招待的实现中,打印素食菜单,调用迭代器遍历即可
public class WaitressV6 {
private MenuComponentV6 rootMenu;
public WaitressV6(MenuComponentV6 rootMenu) {
this.rootMenu = rootMenu;
}
//报全部菜单
public void printMenu(){
System.out.println("菜单:");
rootMenu.print();
}
//报素食菜单
public void printVegetarianMenu(){
System.out.println("素食菜单:");
Iterator<MenuComponentV6> iterator = rootMenu.createIterator();
while(iterator.hasNext()){
MenuComponentV6 component = iterator.next();
try {
if(component.isVegetarian()){
component.print();
}
} catch (UnsupportedOperationException e) {
//组合上的额打印异常忽略。
}
}
}
}
最后测试一波
public class MenuMainV6 {
public static void main(String[] args) {
MenuComponentV6 rootMenu = new MenuCompositeV6("所有菜单", "菜单树根");
MenuComponentV6 pancakeHouseMenu = new MenuCompositeV6("煎饼屋菜单", "早餐");
pancakeHouseMenu.add(new MenuItemV6("I型煎饼", "I型煎饼是纯煎饼,不加任何东西", true, 2.0d));
pancakeHouseMenu.add(new MenuItemV6("II型煎饼", "II型煎饼,加一个鸡蛋", false, 3.0d));
pancakeHouseMenu.add(new MenuItemV6("III型煎饼", "III型煎饼,加里脊肉,好吃", false, 4.0d));
pancakeHouseMenu.add(new MenuItemV6("IIII型煎饼", "IIII型煎饼,又加鸡蛋又加里脊肉,再来点辣酱,爽", false, 5.0d));
rootMenu.add(pancakeHouseMenu);
MenuComponentV6 dinerMenu = new MenuCompositeV6("午餐菜单", "午餐");
dinerMenu.add(new MenuItemV6("红烧茄子套餐", "红烧茄子,好词又营养,还不贵", true, 12.0d));
dinerMenu.add(new MenuItemV6("麻婆豆腐套餐", "麻婆豆腐,麻辣鲜香", true, 13.0d));
dinerMenu.add(new MenuItemV6("酸菜鱼套餐", "可以喝汤的酸菜鱼,又酸又鲜还无刺,你值得拥有", false, 24.0d));
dinerMenu.add(new MenuItemV6("红烧牛肉套餐", "选用定级牛肉,加入各种调料炖煮8个小时,入口即化,好吃到爆", false, 35.0d));
MenuComponentV6 dessertMenu = new MenuCompositeV6("甜点菜单", "甜点");
dessertMenu.add(new MenuItemV6("苹果派", "苹果派冰淇淋..", true, 3.5d));
dessertMenu.add(new MenuItemV6("蓝莓派", "蓝莓派冰淇淋..", true, 5.5d));
dinerMenu.add(dessertMenu);
rootMenu.add(dinerMenu);
MenuComponentV6 cafeMenu = new MenuCompositeV6("咖啡菜单", "晚餐");
cafeMenu.add(new MenuItemV6("简单的热咖啡", "原味咖啡,带感", true, 5.0d));
cafeMenu.add(new MenuItemV6("加奶热咖啡", "热咖啡,家电奶,口感不错", false, 7.0d));
cafeMenu.add(new MenuItemV6("加糖冰咖啡", "冰咖啡,可能放点糖要好点", true, 8.0d));
rootMenu.add(cafeMenu);
WaitressV6 waitress = new WaitressV6(rootMenu);
waitress.printVegetarianMenu();
}
}
输出所有素食菜单
素食菜单:
I型煎饼(v), 2.0
-- I型煎饼是纯煎饼,不加任何东西
红烧茄子套餐(v), 12.0
-- 红烧茄子,好词又营养,还不贵
麻婆豆腐套餐(v), 13.0
-- 麻婆豆腐,麻辣鲜香
苹果派(v), 3.5
-- 苹果派冰淇淋..
蓝莓派(v), 5.5
-- 蓝莓派冰淇淋..
苹果派(v), 3.5
-- 苹果派冰淇淋..
蓝莓派(v), 5.5
-- 蓝莓派冰淇淋..
简单的热咖啡(v), 5.0
-- 原味咖啡,带感
加糖冰咖啡(v), 8.0
-- 冰咖啡,可能放点糖要好点
同样,最后来看下UML图
扩展示例
该扩展示例来自于https://java2blog.com/composite-design-pattern-in-java/。
其讲述了公司的组织架构,包括部门和人员,部门又包含子部门这样的树形机构。示例中演示了总经理下包含经理和开发者,经理下又包含开发者。
首先是组件接口,雇员类
/**
* 组件接口 雇员类
*/
public interface Employee {
public void add(Employee employee);
public void remove(Employee employee);
public Employee getChild(int i);
public String getName();
public double getSalary();
public void print();
}
组合对象类的实现
/**
* 组合对象类 经理类
*/
public class Manager implements Employee {
private String name;
private double salary;
public Manager(String name,double salary){
this.name = name;
this.salary = salary;
}
List<Employee> employees = new ArrayList();
public void add(Employee employee) {
employees.add(employee);
}
public Employee getChild(int i) {
return employees.get(i);
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void print() {
System.out.println("-------------");
System.out.println("Name ="+getName());
System.out.println("Salary ="+getSalary());
System.out.println("-------------");
Iterator<Employee> employeeIterator = employees.iterator();
while(employeeIterator.hasNext()){
Employee employee = employeeIterator.next();
employee.print();
}
}
public void remove(Employee employee) {
employees.remove(employee);
}
}
在操作方法中,迭代其孩子,实现内部递归调用。
叶子节点实现
/**
* 叶子节点 开发者类
*/
public class Developer implements Employee{
private String name;
private double salary;
public Developer(String name,double salary){
this.name = name;
this.salary = salary;
}
public void add(Employee employee) {
//this is leaf node so this method is not applicable to this class.
}
public Employee getChild(int i) {
//this is leaf node so this method is not applicable to this class.
return null;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void print() {
System.out.println("-------------");
System.out.println("Name ="+getName());
System.out.println("Salary ="+getSalary());
System.out.println("-------------");
}
public void remove(Employee employee) {
//this is leaf node so this method is not applicable to this class.
}
}
最后,测试
/**
* 测试
*/
public class CompositeDesignPatternMain {
public static void main(String[] args) {
Employee emp1=new Developer("John", 10000);
Employee emp2=new Developer("David", 15000);
Employee manager1=new Manager("Daniel",25000);
manager1.add(emp1);
manager1.add(emp2);
Employee emp3=new Developer("Michael", 20000);
Manager generalManager=new Manager("Mark", 50000);
generalManager.add(emp3);
generalManager.add(manager1);
generalManager.print();
}
}