组合模式(composite)
我们都知道文件和文件夹的概念,并且文件是可以存放在文件夹中,文件夹中也可以存放其他文件夹。需要设计一个简单的程序来实现文件夹和文件的关系。
实现思路
- 文件夹需要存放文件夹和文件,首先想到的是在文件夹中设计两个集合分别来存放文件夹和文件。
- 有展示文件路径需求时,不清楚在最里层是文件夹还是文件,所以需要把文件夹和文件当做一个对象来处理会更好,都是条目。所以需要创建一个他们共同的父类。
- 对文件夹的设计优化,创建一个集合容器,容器类型是父类,即解决了既要存放文件夹又要存放文件的问题。
有时,将容器和内容作为同一种东西看待,可以很好地帮助我们处理问题,在容器中既可以存放内容,也可以存放小容器,然后在小容器中,又可以继续放入更小的容器。这样,这样就形成了容器结构、递归结构。
创造出这样结构的模式就是组合模式(Composite),能够使容器和内存具有一致性,创造出递归结构的模式。
代码实现
Composite:文件和文件夹的父类
public abstract class Component { public Component(String name) { this.name = name; } /** * 名称 */ protected String name; /** * 展示方法 * @param prefix */ public abstract void show(String prefix); /** * 添加方法 * @param component */ public void add(Component component){ }
Folder:文件夹类
public class Folder extends Component { /** * 存放composite的集合 */ private List<Component> componentList = new ArrayList<>(16); public Folder(String name) { super(name); } @Override public void show(String prefix) { prefix += "/"+name; String finalPrefix = prefix; System.out.println(finalPrefix); componentList.forEach(component -> component.show(finalPrefix)); } @Override public void add(Component component) { componentList.add(component); } }
file:文件类
public class File extends Component { public File(String name) { super(name); } @Override public void show(String prefix) { System.out.println(prefix+"/"+this.name); } }
main方法测试
public static void main(String[] args) { Folder folder = new Folder("根文件夹"); Folder folder1 = new Folder("文件夹1"); folder.add(folder1); File file1 = new File("文件1"); folder1.add(file1); File file2 = new File("文件2"); folder1.add(file2); Folder folder2 = new Folder("文件夹2"); folder.add(folder2); File file3 = new File("文件3"); folder2.add(file3); File file4 = new File("文件4"); folder2.add(file4); File file5 = new File("文件5"); folder2.add(file5); File file6 = new File("文件6"); folder.add(file6); folder.show(""); System.out.println("----------------"); folder1.show(""); }
输出结果:
类图
访问者模式
我们在对类中数据结构执行操作A时,一般会在该类中声明一个方法来完成操作A。但是当需要增加另一种操作B时,就需要再增加一个方法。那么在后续不断增加需求过程中,我们就需要不断地去修改这个类,这样就很不符合开闭原则。
那么我们能不能把数据结构和操作分开,当需要增加操作需求时,只需修改操作类呢?
访问模式就可以实现这样的需求。在该模式中,数据结构与处理被分离开来。编写一个表示“访问者”的类来访问数据 中的元素,并把对各元素的处理交给访问者类。这样,当需要增加新的处理时,我们只需要编 的访问者,然后让数据结构可以接受访问者的访问即可。
代码实现
ELement:数据结构接口
public interface Element { void accept(Visitor visitor); }
Visitor:访问者接口
public interface Visitor { void visit(Element element); }
ConcreteElement:具体数据结构实现类
public class ConcreteElement implements Element{ private String name; private Integer age; @Override public void accept(Visitor visitor) { visitor.visit(this); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "ConcreteElement{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
NameVisitor:修改名称属性的访问者
public class NameVisitor implements Visitor { @Override public void visit(Element element) { ConcreteElement concreteElement = (ConcreteElement) element; concreteElement.setName("测试"); } }
AgeVisitor:修改年龄属性的访问者
public class AgeVisitor implements Visitor { @Override public void visit(Element element) { ConcreteElement concreteElement = (ConcreteElement) element; concreteElement.setAge(1); } }
main方法:
public static void main(String[] args) { Element element = new ConcreteElement(); System.out.println(" 没有被访问结果输出: "+element); Visitor visitor = new NameVisitor(); element.accept(visitor); System.out.println(" 被NameVisitor访问结果输出: "+element); visitor = new AgeVisitor(); element.accept(visitor); System.out.println(" 被AgeVisitor访问结果输出: "+element); }
数据结果:
通过上面的代码实现,可以看到ConcreteElement通过accept实现了对访问者动态变更,通过传入不同的访问者实现类不同的操作需求,后期因需求的增加只需增加不同的访问者。
类图
俩个模式搭配干活
浅尝
需求
在组合模式中,完成了一个文件夹的设计。现在需要增加一个需求:对当前文件夹中的文件做名称修改。
这个需求其实很简单,首先想到应该就是遍历文件夹修改里面的文件名称。
那么有没有更优雅的方式呢?试试访问者模式
代码实现
下面贴入的代码已省略在组合模式已有的代码。
文件相关代码:
public interface Element { void accept(Visitor visitor); } public abstract class Component implements Element{} public class File extends Component { @Override public void accept(Visitor visitor) { visitor.visit(this); } } public class Folder extends Component { @Override public void accept(Visitor visitor) { componentList.stream().forEach(component -> visitor.visit(component)); } }
访问者相关代码:
public interface Visitor { void visit(Element element); } public class UpdateFileNameVisitor implements Visitor { @Override public void visit(Element element) { if(element instanceof Folder){ element.accept(this); }else{ File file = (File) element; file.setName("visitor update-"+file.getName()); } } }
main方法
public static void main(String[] args) { // 省略已有代码 folder.show(""); System.out.println("----------------"); Visitor visitor = new UpdateFileNameVisitor(); folder.accept(visitor); folder.show(""); }
结果:
深入
在我们日常业务代码中,经常会出现树型结构数据。比商品分类,权限等,一般涉及到这样的数据结构可以考虑使用组合模式+访问模式来处理需求。
需求
在商品分类处理中
- 实现商品分类树的查询 。
- 实现商品分类的删除,并删除他的子类。
代码实现
商品分类数据结构
public class CommodityClass { /** * 标识 */ private Long id; /** * 名称 */ private String name; /** * 父分类ID */ private Long parentId; /** * 商品分类子集 */ private List<CommodityClass> Children; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<CommodityClass> getChildren() { return Children; } public void setChildren(List<CommodityClass> children) { Children = children; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public <T> T execute(CommodityClassOperation<T> commodityClassOperation){ return commodityClassOperation.doExecute(this); } @Override public String toString() { return "CommodityClass{" + "id=" + id + ", name='" + name + '\'' + ", parentId=" + parentId + ", Children=" + Children + '}'; } }
service层处理类
public interface CommodityClassService { /** * 查询商品分类结构树 * @param commodityClassId 商品分类ID * @return 商品分类结构树 */ CommodityClass getAllCommodityClassServiceById(Long commodityClassId); /** * 删除商品分类 * @param commodityClassId 商品分类ID * @return true 成功 false 失败 */ Boolean deleteAllCommodityClassServiceById(Long commodityClassId); }
@Service public class CommodityClassServiceImpl implements CommodityClassService { @Autowired private QueryCommodityClassOperation queryCommodityClassOperation; @Autowired private DeleteCommodityClassOperation deleteCommodityClassOperation; @Autowired private CommodityClassDAO commodityClassDAO; /** * 查询商品分类结构树 * @param commodityClassId 商品分类ID * @return 商品分类结构树 */ @Override public CommodityClass getAllCommodityClassServiceById(Long commodityClassId){ CommodityClass commodityClassServiceById = commodityClassDAO.getCommodityClassServiceById(commodityClassId); queryCommodityClassOperation.doExecute(commodityClassServiceById); return commodityClassServiceById; } /** * 删除商品分类 * @param commodityClassId 商品分类ID * @return true 成功 false 失败 */ @Override public Boolean deleteAllCommodityClassServiceById(Long commodityClassId){ CommodityClass commodityClassServiceById = commodityClassDAO.getCommodityClassServiceById(commodityClassId); return deleteCommodityClassOperation.doExecute(commodityClassServiceById); } }
访问者类
public interface CommodityClassOperation<T> { T doExecute(CommodityClass commodityClass); }
@Component public class QueryCommodityClassOperation implements CommodityClassOperation<Boolean> { @Autowired private CommodityClassDAO commodityClassDAO; @Override public Boolean doExecute(CommodityClass commodityClass) { List<CommodityClass> children = commodityClassDAO.listCommodityClassServiceByParentId(commodityClass.getId()); if(!CollectionUtils.isEmpty(children)){ children.stream().forEach(commodityClass1 -> commodityClass1.execute(this)); } commodityClass.setChildren(children); return true; } }
@Component public class DeleteCommodityClassOperation implements CommodityClassOperation<Boolean> { @Autowired private CommodityClassDAO commodityClassDAO; @Override public Boolean doExecute(CommodityClass commodityClass) { List<CommodityClass> children = commodityClassDAO.listCommodityClassServiceByParentId(commodityClass.getId()); if(!CollectionUtils.isEmpty(children)){ children.stream().forEach(commodityClass1 -> commodityClass1.execute(this)); } commodityClassDAO.deletCommodityClassServiceById(commodityClass.getId()); return true; } }
DAO层查询数据库已省略可以自行实现。