组合模式(composite)
我们都知道文件和文件夹的概念,并且文件是可以存放在文件夹中,文件夹中也可以存放其他文件夹。需要设计一个简单的程序来实现文件夹和文件的关系。
实现思路
文件夹需要存放文件夹和文件,首先想到的是在文件夹中设计两个集合分别来存放文件夹和文件。
有展示文件路径需求时,不清楚在最里层是文件夹还是文件,所以需要把文件夹和文件当做一个对象来处理会更好,都是条目。所以需要创建一个他们共同的父类。
对文件夹的设计优化,创建一个集合容器,容器类型是父类,即解决了既要存放文件夹又要存放文件的问题。
有时,将容器和内容作为同一种东西看待,可以很好地帮助我们处理问题,在容器中既可以存放内容,也可以存放小容器,然后在小容器中,又可以继续放入更小的容器。这样,这样就形成了容器结构、递归结构。
创造出这样结构的模式就是组合模式(Composite),能够使容器和内存具有一致性,创造出递归结构的模式。
代码实现
Composite:文件和文件夹的父类
publicabstractclassComponent {
publicComponent(String name) {
this.name = name;
}
/**
* 名称
*/protected String name;
/**
* 展示方法
* @param prefix
*/publicabstractvoidshow(String prefix);
/**
* 添加方法
* @param component
*/publicvoidadd(Component component){
}
Folder:文件夹类
publicclassFolderextendsComponent{
/**
* 存放composite的集合
*/private List<Component> componentList = new ArrayList<>(16);
publicFolder(String name){
super(name);
}
@Overridepublicvoidshow(String prefix){
prefix += "/"+name;
String finalPrefix = prefix;
System.out.println(finalPrefix);
componentList.forEach(component -> component.show(finalPrefix));
}
@Overridepublicvoidadd(Component component){
componentList.add(component);
}
}
file:文件类
publicclassFileextendsComponent{
publicFile(String name){
super(name);
}
@Overridepublicvoidshow(String prefix){
System.out.println(prefix+"/"+this.name);
}
}
main方法测试
publicstaticvoidmain(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("");
}
输出结果:
![](https://img-blog.csdnimg.cn/img_convert/d4bc258898f19e7f6cccfad398d1f262.png)
类图
![](https://img-blog.csdnimg.cn/img_convert/d4ce6ff51a041319454038a057dc23a5.png)
访问者模式
我们在对类中数据结构执行操作A时,一般会在该类中声明一个方法来完成操作A。但是当需要增加另一种操作B时,就需要再增加一个方法。那么在后续不断增加需求过程中,我们就需要不断地去修改这个类,这样就很不符合开闭原则。
那么我们能不能把数据结构和操作分开,当需要增加操作需求时,只需修改操作类呢?
访问模式就可以实现这样的需求。在该模式中,数据结构与处理被分离开来。编写一个表示“访问者”的类来访问数据 中的元素,并把对各元素的处理交给访问者类。这样,当需要增加新的处理时,我们只需要编 的访问者,然后让数据结构可以接受访问者的访问即可。
代码实现
ELement:数据结构接口
publicinterfaceElement {
voidaccept(Visitor visitor);
}
Visitor:访问者接口
publicinterfaceVisitor {
voidvisit(Element element);
}
ConcreteElement:具体数据结构实现类
publicclassConcreteElementimplementsElement{
private String name;
private Integer age;
@Overridepublicvoidaccept(Visitor visitor){
visitor.visit(this);
}
public String getName(){
return name;
}
publicvoidsetName(String name){
this.name = name;
}
public Integer getAge(){
return age;
}
publicvoidsetAge(Integer age){
this.age = age;
}
@Overridepublic String toString(){
return"ConcreteElement{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
NameVisitor:修改名称属性的访问者
publicclassNameVisitorimplementsVisitor{
@Overridepublicvoidvisit(Element element){
ConcreteElement concreteElement = (ConcreteElement) element;
concreteElement.setName("测试");
}
}
AgeVisitor:修改年龄属性的访问者
publicclassAgeVisitorimplementsVisitor{
@Overridepublicvoidvisit(Element element){
ConcreteElement concreteElement = (ConcreteElement) element;
concreteElement.setAge(1);
}
}
main方法:
publicstaticvoidmain(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);
}
数据结果:
![](https://img-blog.csdnimg.cn/img_convert/1bacddcc5acc4e766098956766bb26ad.png)
通过上面的代码实现,可以看到ConcreteElement通过accept实现了对访问者动态变更,通过传入不同的访问者实现类不同的操作需求,后期因需求的增加只需增加不同的访问者。
类图
![](https://img-blog.csdnimg.cn/img_convert/4e4b6f5a4e8ce9d84efcf6c8fe6c3aa8.png)
俩个模式搭配干活
浅尝
需求
在组合模式中,完成了一个文件夹的设计。现在需要增加一个需求:对当前文件夹中的文件做名称修改。
这个需求其实很简单,首先想到应该就是遍历文件夹修改里面的文件名称。
那么有没有更优雅的方式呢?试试访问者模式
代码实现
下面贴入的代码已省略在组合模式已有的代码。
文件相关代码:
publicinterfaceElement{
voidaccept(Visitor visitor);
}
publicabstractclassComponentimplementsElement{}
publicclassFileextendsComponent{
@Overridepublicvoidaccept(Visitor visitor){
visitor.visit(this);
}
}
publicclassFolderextendsComponent{
@Overridepublicvoidaccept(Visitor visitor){
componentList.stream().forEach(component -> visitor.visit(component));
}
}
访问者相关代码:
publicinterfaceVisitor{
voidvisit(Element element);
}
publicclassUpdateFileNameVisitorimplementsVisitor{
@Overridepublicvoidvisit(Element element){
if(element instanceof Folder){
element.accept(this);
}else{
File file = (File) element;
file.setName("visitor update-"+file.getName());
}
}
}
main方法
publicstaticvoidmain(String[] args) {
// 省略已有代码
folder.show("");
System.out.println("----------------");
Visitor visitor = new UpdateFileNameVisitor();
folder.accept(visitor);
folder.show("");
}
结果:
![](https://img-blog.csdnimg.cn/img_convert/09e9350bb25ae94376183852284f86fc.png)
深入
在我们日常业务代码中,经常会出现树型结构数据。比商品分类,权限等,一般涉及到这样的数据结构可以考虑使用组合模式+访问模式来处理需求。
需求
在商品分类处理中
实现商品分类树的查询 。
实现商品分类的删除,并删除他的子类。
代码实现
商品分类数据结构
publicclassCommodityClass {
/**
* 标识
*/private Long id;
/**
* 名称
*/private String name;
/**
* 父分类ID
*/private Long parentId;
/**
* 商品分类子集
*/private List<CommodityClass> Children;
public Long getId() {
return id;
}
publicvoidsetId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
publicvoidsetName(String name) {
this.name = name;
}
public List<CommodityClass> getChildren() {
return Children;
}
publicvoidsetChildren(List<CommodityClass> children) {
Children = children;
}
public Long getParentId() {
return parentId;
}
publicvoidsetParentId(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层处理类
publicinterfaceCommodityClassService{
/**
* 查询商品分类结构树
* @param commodityClassId 商品分类ID
* @return 商品分类结构树
*/
CommodityClass getAllCommodityClassServiceById(Long commodityClassId);
/**
* 删除商品分类
* @param commodityClassId 商品分类ID
* @return true 成功 false 失败
*/Boolean deleteAllCommodityClassServiceById(Long commodityClassId);
}
@ServicepublicclassCommodityClassServiceImplimplementsCommodityClassService{
@Autowiredprivate QueryCommodityClassOperation queryCommodityClassOperation;
@Autowiredprivate DeleteCommodityClassOperation deleteCommodityClassOperation;
@Autowiredprivate CommodityClassDAO commodityClassDAO;
/**
* 查询商品分类结构树
* @param commodityClassId 商品分类ID
* @return 商品分类结构树
*/@Overridepublic CommodityClass getAllCommodityClassServiceById(Long commodityClassId){
CommodityClass commodityClassServiceById = commodityClassDAO.getCommodityClassServiceById(commodityClassId);
queryCommodityClassOperation.doExecute(commodityClassServiceById);
return commodityClassServiceById;
}
/**
* 删除商品分类
* @param commodityClassId 商品分类ID
* @return true 成功 false 失败
*/@OverridepublicBoolean deleteAllCommodityClassServiceById(Long commodityClassId){
CommodityClass commodityClassServiceById = commodityClassDAO.getCommodityClassServiceById(commodityClassId);
return deleteCommodityClassOperation.doExecute(commodityClassServiceById);
}
}
访问者类
publicinterfaceCommodityClassOperation<T> {
T doExecute(CommodityClass commodityClass);
}
@ComponentpublicclassQueryCommodityClassOperationimplementsCommodityClassOperation<Boolean> {
@Autowiredprivate CommodityClassDAO commodityClassDAO;
@Overridepublic 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);
returntrue;
}
}
@ComponentpublicclassDeleteCommodityClassOperationimplementsCommodityClassOperation<Boolean> {
@Autowiredprivate CommodityClassDAO commodityClassDAO;
@Overridepublic 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());
returntrue;
}
}
DAO层查询数据库已省略可以自行实现。
资源获取:
大家 点赞、收藏、关注、评论啦 、 查看 👇🏻 👇🏻 👇🏻 微信公众号获取联系方式 👇🏻 👇🏻 👇🏻
精彩专栏推荐订阅:在 下方专栏 👇🏻 👇🏻 👇🏻 👇🏻
每天学四小时:Java+Spring+JVM+分布式高并发,架构师指日可待