[译文]设计模式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 »

读《洞见》

书名叫《洞见》。原文是 Why Buddhism is TRUE 。看到封面就有点懵,这咋翻译的?

阅读完一半你会发现,Vipassana Meditation,内观禅修,是作者修习的。书中也反复提到。单拎出来,Vipassana,指清晰的视觉,又常译作“洞见”。

终于对上了。

分享一些摘录和所思所想吧。

幻觉——感觉的真与假

有益的就是真,有害的就是假。都是从长远看。

滞后性

甜甜圈

过往:在食物匮乏的年代,甜食代表着能量,是有益的

如今:在健康饮食的理念下,“零卡路里”是有益的

总结:由于进化的滞后性,甜甜圈对我们有益的感觉是假的

路怒症

在小村庄里,你被占了便宜/被压迫/被欺负,表达你的反抗和愤怒是有益的。这传达出了“你不是好欺负的”这一信息。否则,你周围的人会以为你好欺负,都来欺负你。长远看,表达愤怒是好的。

现如今,在开车时对“过客”表达你的愤怒,可能一时有一种爽的感觉,但对开车来说是危险的。

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)) {
      // 男生打篮球
    }
  }
}

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

 

读《一课经济学》

本书的主题是公共经济学。跟《牛奶可乐经济学》类似,用通俗易懂的事例来解释经济学术语。不同的是《牛奶》侧重于对个体的影响,而本书关注的是宏观层面的。

本书可以用一句话概括(原文):经济学的艺术在于不仅要观察任何行为或政策的即期影响,更要考察比较长远的影响;不仅要关注政策对某个群体产生的影响,更要追踪对所有群体造成的影响

好书。还没看完,就打了五星了。好多知识点啊,引人深思。几乎每天看一段都有改变我认知的地方(说颠覆好像有点大)。 例如,限定每周工时的法律法规是政府用来避免失业或提高就业率,这是之前从没想到过的。以前觉得这是人本思想或社会的进步使劳动力得到了解放。

Continue Reading »

[译文]设计模式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. 大量使用了组合模式装饰者模式的设计也可以使用原型模式