设计模式之集合模式

设计模式进阶篇


迭代子

首先,我们可以了解一下什么叫容器:它可以简单地定义为一些对象的集合,容器中的对象也可以是容器。而迭代子模式其实就是允许客户对象以一致的方式访问容器中的内容,而不需要知道这些内容在容器内部是如何表示的。以Java为例子,它的集合就大量采用了这种模式,譬如:ArrayList的源码中,就申明了一个内部类Itr并实现了Iterator接口,并通过iterator()方法返回Itr类的对象。
在这里插入图片描述
其实,它就相当于一个中介,以往客户端遍历容器可能直接用个for或foreach进行遍历,当这种设计模式明显不符合开闭原则,但设计代码重构时,容器或者客户对象做出改变的同时,遍历方式可能也得改变,如果涉及到过滤(其实就是根据条件遍历指定内容)修改的代码可能更多。所以,在Java程序设计中,我们习惯依赖于抽象,而隐藏具体的细节,采用迭代子无疑可以达到这种效果,我们持有一个迭代子引用,不用再去关心容器的内容或者它的变化情况。
在这里插入图片描述
另外,我看了些其他博主写得文章,他们在关于主动迭代子和被动迭代子的说法上,认为主动迭代子是外部迭代子,而被动迭代子则是内部迭代子,这个说法我觉得欠妥。关于主动迭代子和外部迭代子的解释,Java与设计模式一书是这样解释的:
在这里插入图片描述
而Java体系结构与设计模式一书没有主动迭代子和被动迭代子的说法,但对内部迭代子和外部迭代子做出了如下解释:
在这里插入图片描述
通过以上对比推敲,我更倾向于主动迭代子是内部迭代子而不是外部迭代子,当然,由于才疏学浅,如果是我的理解出现差错,还请各位道友指正,或者说一下自己的理解,一定虚心接受。

书上关于内部迭代子和外部迭代子的代码:点击即可,作者采用swing组件读取文本信息,读取方式采用了迭代子,具体内容下载后可以对比、运行看看看。


合成

所有对象或者组件都可以被分类成两种一一独立组件和合成组件。独立的组件由一些单独的组件构成,合成组件由其他复合组件构成。而合成模式用来设计一个公共的接口,既可以提供给独立组件使用,也可以供复合组件使用。这样,客户端程序就可以以一种统一的视角来观察独立组件以及合成组件。换句话讲,合成模式允许一个客户端对象以同样的方式对待单独的组件以及一组对象。这里注意区别迭代子,尽管它们都是集合模式,但关注的点却不一样:
在这里插入图片描述
如果还不能理解,可以看看如下的例子:现在我们想做一个文件系统,主要功能是统计文件大小,然后我们需要考虑存放文件时,我们有时候直接放在根目录,有时候我们放在目录下。对于前者,我们可以直接返回文件的大小,但对于存放在目录的文件我们还需要进行遍历。

如果采用以往的设计方式,我们可能这样做:

在这里插入图片描述
采用这种方式的缺点是,目录存放文件的方式DirComponent有着自己实现的方法,这将在造成客户端不能简单的通过创建接口的引用来访问DirComponent类,因为它有些方法没在接口里。另一种方法就是用到合成,将公共部分设计成抽象类,DirComponent类独有的方法移动到抽象类里,并给他一个默认的实现:

public abstract class FileSystemComponent {
  String name;

  public FileSystemComponent(String cName) {
    name = cName;
  }
  //这样即使是FileComponent 的实例访问到这个方法也没事,因为在FileComponent中没有提供实现,它会返回抽象类的错误提示
  //在DirComponent中,我们可以提供实现,从而覆盖这个方法
  public void addComponent(FileSystemComponent component)
  throws CompositeException {
    throw new CompositeException(
      "Invalid Operation. Not Supported");
  }
  public FileSystemComponent getComponent(int componentNum)
  throws CompositeException {
    throw new CompositeException(
      "Invalid Operation. Not Supported");
  }

  public abstract long getComponentSize();

} // End of class FileSystemComponent


看完上面例子,我们可以知道其实他就是借助Java中多态的概念,通过重写父类方法达到覆盖的目的。


享元

享元是用来解决软件运行时需要创建大量对象,而这些对象又都包含着可变和不可变数据的问题的。享元模式在企业级架构设计中应用的例子比比皆是,现代大型企业级应用中不可或缺的缓存体系(连接池、线程池、对象池…)也正是在享元模式的基础上逐步完善和发展起来的。(顺带提一下:高并发系统的常见优化策略:缓存、集群、切分、异步响应….)

举个例子,一个学校里有多名学生,他们相当于一个个的对象,他们手上都拿着学生卡,在学生卡上,关于学校的信息总是一致的(不考虑乱七八糟的况:学校改名或倒闭),而不一致的只是学生的个人信息,像学号等等。而在计算机中,当大量运行这样的对象时,无疑是消耗资源的。于是,享元提出了解决方案:将共同部分抽取出来作为对象,即享元对象,其他对象共享这个对象里的信息即可。这个用过sping的道友肯定很熟悉,就像它的AOP思想一样。

设计方法有如下两种:第一种是将不变的数据抽象出来成为接口,在实体类中新建一个内部类并实现该接口并返回该内部类的一个对象,客户端的实例共享这个对象。

场景:打印一家公司员工信息,不变的数据有公司名称,地址,变化的数据有员工姓名

package test;

/**
 * @author fang
 *将共同的信息提取出来
 */
public interface FlyweightIntr {
  public String getCompany();
  public String getAddress();
}

下面是一个享元工厂,其中构造方法私有化,确保为单例模式

package test;

import java.util.HashMap;

public class FlyweightFactory {

	private HashMap lstFlyweight;
	  private static FlyweightFactory factory =
	    new FlyweightFactory();

	  private FlyweightFactory() {
	    lstFlyweight = new HashMap();
	  }

	  public synchronized FlyweightIntr getFlyweight(
	    String divisionName) {
	    if (lstFlyweight.get(divisionName) == null) {
	      FlyweightIntr fw = new Flyweight(divisionName);
	      lstFlyweight.put(divisionName, fw);
	      return fw;
	    } else {
	      return (FlyweightIntr) lstFlyweight.get(divisionName);
	    }
	  }
	  public static FlyweightFactory getInstance() {
	    return factory;
	  }
	  //Inner flyweight class,内部类实现了享元接口
	  private class Flyweight implements FlyweightIntr {
	    private String company;
	    private String address;

	    private void setValues(String cmp, String addr) {
	      company = cmp;
	      address = addr;
	    }
	    private Flyweight(String division) {
	      // values are hard coded
	      //for simplicity
	      if (division.equals("North")) {
	        setValues("CMP","addr1");
	      }
	    }
	    public String getCompany() {
	      return company;
	    }
	    public String getAddress() {
	      return address;
	    }
	  }
}

这个是我们想打印员工信息的类

package test;

public class VCard {

	String name;
	FlyweightIntr objFW;

	public VCard(String n, FlyweightIntr flyweight) {
		name = n;
		objFW = flyweight;
	}

	public void print() {
		System.out.println(name);
		System.out.println(objFW.getAddress()+"-"+objFW.getCompany());
		System.out.println("----------------");
	}

}

测试类

package test;

import java.util.*;


public class FlyweightTest {
	public static void main(String[] args) {
		Vector empList = initialize();//初始化数据
		
		FlyweightFactory factory = FlyweightFactory.getInstance();

		for(int i = 0;i<empList.size();i++){
			String s = (String) empList.elementAt(i);
			StringTokenizer st = new StringTokenizer(s, ",");
			String name = st.nextToken();
			String division = st.nextToken();
			// System.out.println(division);
			FlyweightIntr flyweight = factory.getFlyweight(division); //根据division进行判断,我这里只写了north,
			//表示公司只有北方的,其他地方没有分公司,将共同信息变为享元
			// associate the flyweight
			// with the extrinsic data object.
			VCard card = new VCard(name, flyweight);
			card.print();
		}
	}
	private static Vector initialize() {
		//for simplicity values are being hardcoded.
	    Vector v = new Vector();
	    v.add("name1,North");
	    v.add("name3,North");
	    return v;
	}
}



结果

name1
addr1-CMP
----------------
name2
addr1-CMP
----------------
name3
addr1-CMP
----------------

第二种方法,在第一种上改进,享元接口添加了打印的方法,这样Vcard就没有存在的必要了。

public interface FlyweightIntr {
  public String getCompany();
  public String getAddress();
  public String getCity();
  public String getState();
  public String getZip();
  public void print(String name, String title);
}

内部类增加打印方法的实现

//Inner flyweight class
  private class Flyweight implements FlyweightIntr {
    private String company;
    private String address;
   

    private void setValues(String cmp, String addr) {

      company = cmp;
      address = addr;
    }

    private Flyweight(String division) {
      // values are hard coded
      //for simplicity
      if (division.equals("North")) {
        setValues("CMP","addr1", "cty1","st1","10000");
      }
    }

    public String getCompany() {
      return company;
    }
    public String getAddress() {
      return address;
    }
    public void print(String name) {
      System.out.println(name);
      System.out.println(getAddress() + "-" + getCompany());
      System.out.println("----------------");
    }
  }

因为共同信息是固定的,在初始化享元后,我们就不需要管了,打印员工信息时,调用print方法,传入参数员工姓名即可。


访问者

访问者模式是用来解决对集合里面的不同对象进行统一操作的问题的。这个地方,我们可以类比合成模式,在合成模式中,我们定义一个抽象类,并把子类的方法移至抽象类中,并对那些非公共部分的方法提供默认的实现,此时,我们通过抽象类就可以定义一个跨越不同对象的操作了,但是,每当需要新的操作时,我们的子类总是需要做出改变,这名想破坏了OCP,开闭原则,采用访问者而模式可以弥补这一问题。

现在假设一个公司分为领导和员工两个群体,我们统计这个两个群体分别的总工资:

package top.fang.visitor;

/**
 * 定义一个访问者接口,里面纳入不同对象的访问方法visit
 */
public interface visitorInterface {
    void visit(Leader lea);
    void visit(Staff sta);
}

package top.fang.visitor;

/***
 * 工资接口,领导和员工都要实现accept方法,并把自己传入参数
 */
public interface Salary {
    void accept(SalaryVisitor v);
}

package top.fang.visitor;

/**
 * 普通员工
 */
public class Staff implements Salary{
    private double staffSalary;
    public Staff(){

    }
    public Staff(double salary){
        this.staffSalary = salary;
    }
    public double getStaffSalary() {
        return staffSalary;
    }
    @Override
    public void accept(SalaryVisitor v) {
        v.visit(this);
    }


}

package top.fang.visitor;

/**
 * 领导
 */
public class Leader implements Salary {
    private double leaderSalary;
    public Leader(){

    }
    public Leader(double salary){
        this.leaderSalary = salary;
    }
    public double getLeaderSalary() {
        return leaderSalary;
    }
    @Override
    public void accept(SalaryVisitor v) {
        v.visit(this);
    }
}

package top.fang.visitor;

/**
 * 工资访问者,统一访问员工和领导的工资
 */
public class SalaryVisitor implements visitorInterface {
    private double staffCountSalary;
    private  double leaderCountSalary;
    @Override
    public void visit(Leader lea) {
        leaderCountSalary +=lea.getLeaderSalary();
    }

    @Override
    public void visit(Staff sta) {
        staffCountSalary += sta.getStaffSalary();
    }

    public double getStaffCountSalary() {
        return staffCountSalary;
    }

    public double getLeaderCountSalary() {
        return leaderCountSalary;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

legendaryhaha

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值