文章标签 » Java

[译文]设计模式13 – 私有数据对象模式(附代码实例)

原文网址:Private Class Data Design Pattern

意图

  • 控制类属性的写访问
  • 将数据和使用它的方法分开
  • 封装类对象的初始化过程
  • 提供新型的 final —— 在构造之后不可变

问题

一个类可能将它的属性(实例变量)暴露给它的方法,即使是这个方法不再需要,例如构造函数。使用私有数据对象模式可以避免这种不良操作。

一个类可能有一次性的不可变的属性,但它又不能定义为 final 类型。这种情况可以使用该设计模式完成一次性设定。

该模式的设计灵感来自于通过缩小属性(数据)的可见性来保护类状态的设计目标。

讨论

私有数据对象模式旨在通过限制属性的可见性来减少其暴露。

它将属性封装进单独的数据对象从而减少了类属性的数量。它允许类的设计者移除那些只允许在构造器中设置的属性的特殊的写操作,甚至是在类的内部也不可更改(final)。

结构

私有数据对象模式通过在目标类中提取一个数据类解决了上述问题,并且在目标类的实例中包含了一个数据类的引用。

核查清单(也可以理解为应用步骤)

  1. 创建数据类。将所有需要隐藏的属性都移入数据类中
  2. 在主类中创建一个数据类的引用
  3. 主类必须通过数据类的构造函数来初始化数据类
  4. 通过 getter 方法将数据类中的属性对外暴露
  5. 通过 setter 方法暴露以后可能需要修改的属性

[译文]设计模式12 – 蝇量模式(附代码实例)

原文网址:Flyweight Design Pattern

意图

  • 通过共享来高效地使用大量的细粒度的对象
  • 用轻量级组件代替重量级组件

问题

对象设计粒度越细越能提供更好的灵活性,但就性能和内存的使用而言,代价可能高得令人无法接受。

讨论

蝇量设计模式描述了如何在合适的粒度上共享对象并且避免过高的开销。每一个”蝇量”对象被分成两个部分:状态依赖的(外在的)部分和非状态依赖的(内在的)部分。内在的状态(共享的)被存储在蝇量对象里。外在的状态被客户端对象存储或计算,在调用蝇量对象时传递给它。

Motif组件就是一个例证,它已被重新设计为轻量级的组件。各组件已经足够的“智能”而独立,各组件与它们所在的布局管理组件有一种依赖关系。每一个布局管理器提供了上下文相关的事件处理机制、资源管理和服务给蝇量组件,而每一个蝇量组件只有跟上下文无关的状态和行为。

结构

蝇量对象存储在工厂的仓库中。客户端克制自己不直接创建蝇量,而是从工厂获取。蝇量对象不能孤立地存在。任何可能导致不能共享的属性必须由客户端在调用蝇量对象时提供。如果上下文有助于“规模经济”(即客户可以很容易地计算或查找所需的属性),则蝇量模式将提供合适的杠杆。

Ant (蚂蚁)类,Locust (蝗虫)类和 Cockroach (蟑螂)类可以被轻量化是因为他们实例相关的状态被反封装或者外在化了,而且必须由客户端提供。

举例

现代浏览器使用这种技术来防止同样的图片被重复加载。当浏览器加载一个页面时,它遍历了页面上所有的图片。浏览器从网络上获取新的图片然后将他们放在缓存中。对已经加载过的图片,蝇量对象被创建了,它会有一些唯一的属性(如在页面中的位置),但其他的都指向了缓存数据。

核查清单(也可以理解为应用步骤)

  1. 确认当前的对象就是需要关注的问题,同时客户端能够并且愿意接受责任的转移。
  2. 将目标对象的状态分为可共享的(内在的)和不可共享的(外在的)。
  3. 将不可共享的状态从类属性中移除,把它作为调用方法的参数。
  4. 创建一个工厂用来缓存和重用已经存在的实例。
  5. 客户端必须使用这个工厂类而不是 new 获取对象。
  6. 客户端(或者第三方)必须找到或计算出不可共享的状态,然后在调用方法时提供这些状态。

经验法则

  • 解释器抽象语法树中的终端符号可以通过蝇量实现共享

(译者注:详细的代码实例请移步 GitHub

[译文]设计模式11 – 门面模式/外观模式(附代码实例)

原文网址:Facade Design Pattern

意图

  • 为一组子系统的接口提供一个统一的接口。门面模式定义了一个更高层次的接口使得子系统更容易使用
  • 用一个简单的接口封装复杂的子系统

问题

对于整体功能比较复杂的子系统,有一部分客户群体需要一个更简洁的接口。

讨论

门面模式讨论的是将一个复杂的子系统封装在一个接口对象里。这降低了子系统和用户之间的耦合性,也减少了使用子系统的学习曲线。另一方面,如果”门面”是子系统的唯一访问点,它会限制高级用户可能需要的一些特性和灵活性。

门面对象应该是一个相当简单的支持或促进的角色,而不是一个全知全能的先知或者神。

Continue Reading »

[译文]设计模式10 – 装饰者模式(附代码实例)

原文网址:Decorator Design Pattern

意图

  • 动态地给对象添加新的责任。装饰者模式提供了一种灵活方式去创建子类以便扩展功能
  • 由客户端决定对核心类一层层地包装
  • 包装一个礼物,把它放在盒子里,再去包装这个盒子

问题

你想要在运行时给对象增加行为或状态。继承不够灵活,因为它是静态的而且应用到整个类上。

讨论

假设你在开发一个视窗类的工具,你想要给窗口加上边框和滚动条。你可能会这样定义继承关系:

但是装饰者模式建议给用户自由组合任何他想要的特性。

Continue Reading »

[译文]设计模式09 – 复合模式(附代码实例)

原文网址:Composite Design Pattern

意图

  • 将对象复合成树形结构来表示“整体-部分”的层次结构。复合模式使客户端能够统一地处理单个对象和复合对象
  • 递归复合
  • “目录下有多个条目,每个条目也可能是个目录”
  • 将一对多的“has-a”结构变为“is-a”结构

问题

应用程序需要处理包含原始对象和复合对象的分层集合。处理原始对象是一种方式,处理复合对象是另一种方式。每次在处理前都要检查一下对象所属的类别是不可取的。

讨论

定义一个抽象基类(Component),它指定了需要在原始对象和复合对象中统一执行的行为。原始对象和复合对象都继承Component类。只有在管理自己的“孩子”时,复合类才去关联抽象基类。

遇到类似”目录下有多个条目,每个条目也可能是个目录”的场景,可以使用这种模式。

通常,管理“孩子”的方法(如 addChild() removeChild())应该被定义在复合对象里。不幸的是,统一处理原始对象和复合对象的需求使得这些方法必须移到抽象类Component中。参见下方“观点”里关于“安全性”和“透明度”的讨论。

Continue Reading »

[译文]设计模式08 – 桥接模式(附代码实例)

原文网址:Bridge Design Pattern

意图

  • 将抽象与实现分离,以便两者可以独立地改变
  • 在一个继承结构上发布接口,并将实现隐藏在自己的继承机构中
  • 不仅有封装性,还独立

问题

通过继承抽象基类来完成不同实现的方式已经使软件变得“僵化”。它会在编译期间使接口和实现绑定,接口和实现不能独立的扩展或者组合。

Continue Reading »

Java | 对enum的扩展

遇到有一堆枚举值时,很容易就能想到 enum
比如性别(Gender),有男(Male)有女(Female),很容易就会写出下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Gender {
  MALE("男"),
  FEMALE("女");
  
  private final String display;
  
  Gender(String display) {
    this.display = display;
  }
  
  public String getDisplay() {
    return display;
  }
}

可以这样使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
  String name;
  String gender;
  
  // 男女有别
  public void doSomething() {
    Gender g = Gender.valueOf(gender);
    if (Gender.FEMALE == g) {
      // 女生啦啦队
    } else if (Gender.MALE == g) {
      // 男生打篮球
    }
  }
}

以上代码要想正常执行,有个前提条件,属性 gender 的值必须是 FEMALE 或者 MALE。如果数据库中存储的是 FM 呢?甚至是 12
这时,valueOf 方法已经不适用了。因为其参数值必须跟定义的枚举值完全一致。
针对这种情况,我想到了一个扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接口名想了好久,不知道用什么合适
public interface CodeEnum {
  boolean isIt(String code); // 方法名也拿不定主意
}

public enum Gender implements CodeEnum {
  // 用F-M还是1-2,这里可以随时调整
  MALE("M", "男"),
  FEMALE("F", "女");
  
  private final String display;
  
  Gender(String display) {
    this.display = display;
  }
  
  public boolean isIt(String code) {
    return this.code.equals(code);
  }
  
  public String getDisplay() {
    return display;
  }
}

这时,Student 类可以调整为:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student {
  String name;
  String gender;
  
  // 男女有别
  public void doSomething() {
    if (Gender.FEMALE.isIt(gender)) {
      // 女生啦啦队
    } else if (Gender.MALE.isIt(gender)) {
      // 男生打篮球
    }
  }
}

抛砖引玉,有更好的想法欢迎留言讨论。

 

[译文]设计模式07 – 适配器模式(附代码实例)

原文网址:Adapter Design Pattern

意图

  • 将一个类的接口(公共的对外的方法)转换成另一个客户想要的接口。适配器使得原本不兼容的类可以一起协作
  • 给已存在的类封装一个新的接口
  • 使旧组件能够适配新系统

问题

现有的组件包含了一些不得不用的功能,但它已不能适应当前系统的架构和设计理念。

讨论

重用一直是痛苦的和难以捉摸的。原因之一在于设计新东西时不得不使用旧的东西简直是个灾难。新和旧之间总会有一些不协调的地方:可能是客观上的不兼容,可能是时序或同步问题,可能是一些错误的假设或竟态标准。

Continue Reading »

如何在Spring管理的事务中使用多线程

keywords:Java, Spring, Transactional, Multi-threading, ExecutorService

为什么要用多线程?

1
2
3
4
5
6
7
8
9
10
11
12
public class BigService {

    public void execute() {
        doSomething();

	List entities = buildManyManyEntities();
	save(entities);

	doOthers();
    }

}

如上的业务代码,经测试大部分时间花在了 save 方法上(耗时:15/22,数据量:5000)。

于是想到了用多线程(entities 数据之间互不影响)。

于是改成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BigService {

    public void execute() {
	doSomething();

	List entities = buildManyManyEntities();
	// 使用多线程存储
	saveInParallel(entities);

	doOthers();
    }

    void save(Object entities) {
	// 直接存入数据库
	// 单个对象和集合都可以
    }

    void saveInParallel(List entities) {
	int cpus = RunTime.getRunTime().availableProcessors();
	ExecuteService exec = Executors.newFixedThreadPool(cpus);

	try {
	    for (final Object each : entities) {   
		exec.execute(new Runnable() {
			public void run() {
			    save(each);
			}
		});
	    }
	} finally {
	    exec.shutdown(); // 关闭资源
	}
    }
}

自我感觉良好。结果一测试发现表里没数据。

网上查了一下,找到了原因:新的线程没受Spring控制(项目中事务是由Spring控制的)。

知道了问题,就有了方向。意料之外,情理之中地在Stack Overflow(链接见文末)找到了答案。关键点在于Spring中的Lookup,详述如下。

关键步骤在于由Spring来控制线程类的实例化。

新建多线程工具类:MultiThreadHelper.java 。

1
2
3
4
5
public abstract class MultiThreadHelper {
    // 必须是抽象方法且无参
    // 详见文末Spring官方文档Lookup部分
    public abstract DAOTask createDAOTask();
}

新建线程类:DAOTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DAOTask implements Runnable {
    private DAO dao;
    private Object entity;

    public DAOTask(DAO dao) {
	this.dao = dao;
    }

    public void addEntity(Object entity) {
	this.entity = entity;
    }

    public void run() {
	dao.save(entity);
    }
}

新建Spring的配置文件: multi.xml 。

1
2
3
4
5
6
7
8
<bean id="multiThreadHelper" class="MultiThreadHelper">
    <lookup-method name="createDAOTask" bean="daoTask"/>
</bean>

<!-- 注意要用prototype,每次都生成一个新的 -->
<bean id="daoTask" class="DAOTask" scope="prototype">
    <constructor-arg ref="dao"/>
</bean>

新的BigService类(省略了Spring配置文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class BigService {

    private MultiThreadHelper multiThreadHelper; // 通过Spring注入

    public void execute() {
	doSomething();

	List entities = buildManyManyEntities();
	// 使用多线程存储
	saveInParallel(entities);

	doOthers();
    }

    void save(Object entities) {
	// 直接存入数据库
	// 单个对象和集合都可以
    }

    void saveInParallel(List entities) {
	int cpus = RunTime.getRunTime().availableProcessors();
	ExecuteService exec = Executors.newFixedThreadPool(cpus);

	try {
	    for (final Object each : entities) {   
		// 一定要调用这个Lookup方法生成线程
		DAOTask task = getMultiThreadHelper().createDAOTask();
		task.addEntity(each);
		exec.execute(task);
	    }
	} finally {
	    exec.shutdown(); // 关闭资源
	}
    }
}

最后需要注意的是,要把 run 方法纳入事务范围。传播类型REQUIREDREQUIRES_NEW 没看出明显差别。

大功告成!

时间从本来的22s左右,减少为4s左右。当然,这仅仅是响应时间(从调用开始到调用结束),不代表数据都已经存入库中了(通过日志可以看出来)。

线程数量的选择

之前看过的一本书(《Java并发编程实战》)里说:线程数量等于服务器的CPU数量+1时能实现最优的利用率。

于是分别测试了N+1,N,N-1三种情况,差别不大,N-1的略胜一筹。不知道是不是仅仅因为需要创建的线程数少了,导致响应时间变短。

说明:这里的N指CPU的数量,测试中是4 。

问题

这里存在一个问题:事务不能跟主线程一起统一管理,即如果主线程中发生了错误多线程写入的数据不会回滚。

不过有个小技巧——先删后插,每次调这个方法前都先删一下。

如有更好的方法,欢迎留言讨论。

参考链接

 

[译文]设计模式分类(1/3):创建型

原文网址:Creational Patterns

在软件工程领域,创建型设计模式是用来处理对象创建机制的模式——在特定场景下用更合适的方式去创建对象。原始的对象创建方式(如Java的new)可能会导致设计问题或者增加设计的复杂度。创建型设计模式通过控制对象的创建在一定程度上解决了这个问题。

经验法则

  1. 有时候创建型设计模式之间有竞争关系:有些场景,原型模式抽象工厂模式都可以很好地解决;有时候它们又是互补的:抽象工厂模式会用原型模式来存放将要返回的对象;构造器模式可以使用其他的模式去构造组件;抽象工厂模式构造器模式原型模式在实现的过程中可以使用单例模式
  2. 抽象工厂模式构造器模式原型模式定义了一个工厂对象,用来创建对象实例,并且提供入参来实现不同的行为。抽象工厂模式的工厂对象用来创建不同对象的实例。构造器模式的工厂对象根据相应的复杂的协议逐步地构建一个复杂的对象。原型模式的工厂对象通过复制原型对象的方式构建对象。
  3. 抽象工厂模式通常使用工厂方法模式来实现,但也有用原型模式实现的。
  4. 抽象工厂模式在隐藏平台特定的类方面可以作为门面模式的替代品。
  5. 构造器模式专注于一步步地构建复杂的对象。抽象工厂模式着重于一组相似的对象(简单或复杂的)。构造器模式在最后一步完成对象的组装并返回,而抽象工厂模式会立即返回对象。
  6. 构造器模式是一种构建过程,策略模式是一种算法。
  7. 构造器模式通常构成了组合模式
  8. 工厂方法模式通常在模板方法模式中被使用。
  9. 工厂方法模式使用继承创建对象;原型模式通过委托创建对象。
  10. 通常,设计是从工厂方法模式开始,因为它简单、可定制化、子类可扩展。当需要更多灵活性时,就逐步地向抽象工厂模式原型模式构造器模式转变和演化,因为他们更灵活,但也更复杂。
  11. 原型模式不需要子类,但一定要有初始化过程;工厂方法模式需要子类,但不一定有初始化过程。
  12. 大量使用了组合模式装饰者模式的设计也可以使用原型模式