mirror of
https://github.com/LingCoder/OnJava8.git
synced 2026-08-24 06:53:27 +08:00
fix issue:#161 (#364)
* Fix issue:#112 翻译Summary Signed-off-by: crimson <1291463831@qq.com> * fix issue:#161 翻译工厂模式 Signed-off-by: crimson <1291463831@qq.com> * fix issue:#161 Signed-off-by: crimson <1291463831@qq.com>
This commit is contained in:
@@ -425,8 +425,368 @@ Spinning
|
||||
<!-- Factories: Encapsulating Object Creation -->
|
||||
## 工厂模式
|
||||
|
||||
当你发现必须将新类型添加到系统中时,合理的第一步是使用多态性为这些新类型创建一个通用接口。这会将你系统中的其余代码与要添加的特定类型的信息分开,使得可以在不改变现有代码的情况下添加新类型……或者看起来如此。起初,在这种设计中,似乎你必须更改代码的唯一地方就是你继承新类型的地方,但这并不是完全正确的。 你仍然必须创建新类型的对象,并且在创建时必须指定要使用的确切构造器。因此,如果创建对象的代码分布在整个应用程序中,那么在添加新类型时,你将遇到相同的问题——你仍然必须追查你代码中新类型碍事的所有地方。恰好是类型的创建碍事,而不是类型的使用(通过多态处理),但是效果是一样的:添加新类型可能会引起问题。
|
||||
|
||||
解决方案是强制对象的创建都通过通用工厂进行,而不是允许创建代码在整个系统中传播。 如果你程序中的所有代码都必须执行通过该工厂创建你的一个对象,那么在添加新类时只需要修改工厂即可。
|
||||
|
||||
由于每个面向对象的程序都会创建对象,并且很可能会通过添加新类型来扩展程序,因此工厂是最通用的设计模式之一。
|
||||
|
||||
举例来说,让我们重新看一下**Shape**系统。 首先,我们需要一个用于所有示例的基本框架。 如果无法创建**Shape**对象,则需要抛出一个合适的异常:
|
||||
|
||||
```java
|
||||
// patterns/shapes/BadShapeCreation.java package patterns.shapes;
|
||||
public class BadShapeCreation extends RuntimeException {
|
||||
public BadShapeCreation(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接下来,是一个**Shape**基类:
|
||||
|
||||
```java
|
||||
// patterns/shapes/Shape.java
|
||||
package patterns.shapes;
|
||||
public class Shape {
|
||||
private static int counter = 0;
|
||||
private int id = counter++;
|
||||
@Override
|
||||
public String toString(){
|
||||
return getClass().getSimpleName() + "[" + id + "]";
|
||||
}
|
||||
public void draw() {
|
||||
System.out.println(this + " draw");
|
||||
}
|
||||
public void erase() {
|
||||
System.out.println(this + " erase");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该类自动为每一个**Shape**对象创建一个唯一的`id`。
|
||||
|
||||
`toString()`使用运行期信息来发现特定的**Shape**子类的名字。
|
||||
|
||||
现在我们能很快创建一些**Shape**子类了:
|
||||
|
||||
```java
|
||||
// patterns/shapes/Circle.java
|
||||
package patterns.shapes;
|
||||
public class Circle extends Shape {}
|
||||
```
|
||||
|
||||
```java
|
||||
// patterns/shapes/Square.java
|
||||
package patterns.shapes;
|
||||
public class Square extends Shape {}
|
||||
```
|
||||
|
||||
```java
|
||||
// patterns/shapes/Triangle.java
|
||||
package patterns.shapes;
|
||||
public class Triangle extends Shape {}
|
||||
```
|
||||
|
||||
工厂是具有能够创建对象的方法的类。 我们有几个示例版本,因此我们将定义一个接口:
|
||||
|
||||
```java
|
||||
// patterns/shapes/FactoryMethod.java
|
||||
package patterns.shapes;
|
||||
public interface FactoryMethod {
|
||||
Shape create(String type);
|
||||
}
|
||||
```
|
||||
|
||||
`create()`接收一个参数,这个参数使其决定要创建哪一种**Shape**对象,这里是`String`,但是它其实可以是任何数据集合。对象的初始化数据(这里是字符串)可能来自系统外部。 这个例子将测试工厂:
|
||||
|
||||
```java
|
||||
// patterns/shapes/FactoryTest.java
|
||||
package patterns.shapes;
|
||||
import java.util.stream.*;
|
||||
public class FactoryTest {
|
||||
public static void test(FactoryMethod factory) {
|
||||
Stream.of("Circle", "Square", "Triangle",
|
||||
"Square", "Circle", "Circle", "Triangle")
|
||||
.map(factory::create)
|
||||
.peek(Shape::draw)
|
||||
.peek(Shape::erase)
|
||||
.count(); // Terminal operation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在主函数`main()`里,要记住除非你在最后使用了一个终结操作,否则**Stream**不会做任何事情。在这里,`count()`的值被丢弃了。
|
||||
|
||||
创建工厂的一种方法是显式创建每种类型:
|
||||
|
||||
```java
|
||||
// patterns/ShapeFactory1.java
|
||||
// A simple static factory method
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
import patterns.shapes.*;
|
||||
public class ShapeFactory1 implements FactoryMethod {
|
||||
public Shape create(String type) {
|
||||
switch(type) {
|
||||
case "Circle": return new Circle();
|
||||
case "Square": return new Square();
|
||||
case "Triangle": return new Triangle();
|
||||
default: throw new BadShapeCreation(type);
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
FactoryTest.test(new ShapeFactory1());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```java
|
||||
Circle[0] draw
|
||||
Circle[0] erase
|
||||
Square[1] draw
|
||||
Square[1] erase
|
||||
Triangle[2] draw
|
||||
Triangle[2] erase
|
||||
Square[3] draw
|
||||
Square[3] erase
|
||||
Circle[4] draw
|
||||
Circle[4] erase
|
||||
Circle[5] draw
|
||||
Circle[5] erase
|
||||
Triangle[6] draw
|
||||
Triangle[6] erase
|
||||
```
|
||||
|
||||
`create()`现在是添加新类型的Shape时系统中唯一需要更改的其他代码。
|
||||
|
||||
### 动态工厂
|
||||
|
||||
前面例子中的**静态**`create()`方法强制所有创建操作都集中在一个位置,因此这是添加新类型的**Shape**时唯一必须更改代码的地方。这当然是一个合理的解决方案,因为它把创建对象的过程限制在一个框内。但是,如果你在添加新类时无需修改任何内容,那就太好了。 以下版本使用反射在首次需要时将**Shape**的构造器动态加载到工厂列表中:
|
||||
|
||||
```java
|
||||
// patterns/ShapeFactory2.java
|
||||
import java.util.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.stream.*;
|
||||
import patterns.shapes.*;
|
||||
public class ShapeFactory2 implements FactoryMethod {
|
||||
Map<String, Constructor> factories = new HashMap<>();
|
||||
static Constructor load(String id) {
|
||||
System.out.println("loading " + id);
|
||||
try {
|
||||
return Class.forName("patterns.shapes." + id)
|
||||
.getConstructor();
|
||||
} catch(ClassNotFoundException |
|
||||
NoSuchMethodException e) {
|
||||
throw new BadShapeCreation(id);
|
||||
}
|
||||
}
|
||||
public Shape create(String id) {
|
||||
try {
|
||||
return (Shape)factories
|
||||
.computeIfAbsent(id, ShapeFactory2::load)
|
||||
.newInstance();
|
||||
} catch(InstantiationException |
|
||||
IllegalAccessException |
|
||||
InvocationTargetException e) {
|
||||
throw new BadShapeCreation(id);
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
FactoryTest.test(new ShapeFactory2());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```java
|
||||
loading Circle
|
||||
Circle[0] draw
|
||||
Circle[0] erase
|
||||
loading Square
|
||||
Square[1] draw
|
||||
Square[1] erase
|
||||
loading Triangle
|
||||
Triangle[2] draw
|
||||
Triangle[2] erase
|
||||
Square[3] draw
|
||||
Square[3] erase
|
||||
Circle[4] draw
|
||||
Circle[4] erase
|
||||
Circle[5] draw
|
||||
Circle[5] erase
|
||||
Triangle[6] draw
|
||||
Triangle[6] erase
|
||||
```
|
||||
|
||||
和之前一样,`create()`方法基于你传递给它的**String**参数生成新的**Shape**s,但是在这里,它是通过在**HashMap**中查找作为键的**String**来实现的。 返回的值是一个构造器,该构造器用于通过调用`newInstance()`创建新的**Shape**对象。
|
||||
|
||||
然而,当你开始运行程序时,工厂的`map`为空。`create()`使用`map`的`computeIfAbsent()`方法来查找构造器(如果该构造器已存在于`map`中)。如果不存在则使用`load()`计算出该构造器,并将其插入到`map`中。 从输出中可以看到,每种特定类型的**Shape**都是在第一次请求时才加载的,然后只需要从`map`中检索它。
|
||||
|
||||
### 多态工厂
|
||||
|
||||
《设计模式》这本书强调指出,采用“工厂方法”模式的原因是可以从基本工厂中继承出不同类型的工厂。 再次修改示例,使工厂方法位于单独的类中:
|
||||
|
||||
```java
|
||||
// patterns/ShapeFactory3.java
|
||||
// Polymorphic factory methods
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.*;
|
||||
import patterns.shapes.*;
|
||||
interface PolymorphicFactory {
|
||||
Shape create();
|
||||
}
|
||||
class RandomShapes implements Supplier<Shape> {
|
||||
private final PolymorphicFactory[] factories;
|
||||
private Random rand = new Random(42);
|
||||
RandomShapes(PolymorphicFactory... factories){
|
||||
this.factories = factories;
|
||||
}
|
||||
public Shape get() {
|
||||
return factories[ rand.nextInt(factories.length)].create();
|
||||
}
|
||||
}
|
||||
public class ShapeFactory3 {
|
||||
public static void main(String[] args) {
|
||||
RandomShapes rs = new RandomShapes(
|
||||
Circle::new,
|
||||
Square::new,
|
||||
Triangle::new);
|
||||
Stream.generate(rs)
|
||||
.limit(6)
|
||||
.peek(Shape::draw)
|
||||
.peek(Shape::erase)
|
||||
.count();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```java
|
||||
Triangle[0] draw
|
||||
Triangle[0] erase
|
||||
Circle[1] draw
|
||||
Circle[1] erase
|
||||
Circle[2] draw
|
||||
Circle[2] erase
|
||||
Triangle[3] draw
|
||||
Triangle[3] erase
|
||||
Circle[4] draw
|
||||
Circle[4] erase
|
||||
Square[5] draw
|
||||
Square[5] erase
|
||||
```
|
||||
|
||||
**RandomShapes**实现了**Supplier \<Shape>**,因此可用于通过`Stream.generate()`创建**Stream**。 它的构造器采用**PolymorphicFactory**对象的可变参数列表。 变量参数列表以数组形式出现,因此列表是以数组形式在内部存储的。`get()`方法随机获取此数组中一个对象的索引,并在结果上调用`create()`以产生新的**Shape**对象。 添加新类型的**Shape**时,**RandomShapes**构造器是唯一需要更改的地方。 请注意,此构造器需要**Supplier \<Shape>**。 我们传递给其**Shape**构造器的方法引用,该引用可满足**Supplier \<Shape>**约定,因为Java 8支持结构一致性。
|
||||
|
||||
鉴于**ShapeFactory2.java**可能会抛出异常,使用此方法则没有任何异常——它在编译时完全确定。
|
||||
|
||||
### 抽象工厂
|
||||
|
||||
抽象工厂模式看起来像我们之前所见的工厂对象,但拥有不是一个工厂方法而是几个工厂方法, 每个工厂方法都会创建不同种类的对象。 这个想法是在创建工厂对象时,你决定如何使用该工厂创建的所有对象。 《设计模式》中提供的示例实现了跨各种图形用户界面(GUI)的可移植性:你创建一个适合你正在使用的GUI的工厂对象,然后从中请求菜单,按钮,滑块等等,它将自动为GUI创建适合该项目版本的组件。 因此,你可以将从一个GUI更改为另一个所产生的影响隔离限制在一处。 作为另一个示例,假设你正在创建一个通用游戏环境来支持不同类型的游戏。 使用抽象工厂看起来就像下文那样:
|
||||
|
||||
```java
|
||||
// patterns/abstractfactory/GameEnvironment.java
|
||||
// An example of the Abstract Factory pattern
|
||||
// {java patterns.abstractfactory.GameEnvironment}
|
||||
package patterns.abstractfactory;
|
||||
import java.util.function.*;
|
||||
interface Obstacle {
|
||||
void action();
|
||||
}
|
||||
|
||||
interface Player {
|
||||
void interactWith(Obstacle o);
|
||||
}
|
||||
|
||||
class Kitty implements Player {
|
||||
@Override
|
||||
public void interactWith(Obstacle ob) {
|
||||
System.out.print("Kitty has encountered a ");
|
||||
ob.action();
|
||||
}
|
||||
}
|
||||
|
||||
class KungFuGuy implements Player {
|
||||
@Override
|
||||
public void interactWith(Obstacle ob) {
|
||||
System.out.print("KungFuGuy now battles a ");
|
||||
ob.action();
|
||||
}
|
||||
}
|
||||
|
||||
class Puzzle implements Obstacle {
|
||||
@Override
|
||||
public void action() {
|
||||
System.out.println("Puzzle");
|
||||
}
|
||||
}
|
||||
|
||||
class NastyWeapon implements Obstacle {
|
||||
@Override
|
||||
public void action() {
|
||||
System.out.println("NastyWeapon");
|
||||
}
|
||||
}
|
||||
|
||||
// The Abstract Factory:
|
||||
class GameElementFactory {
|
||||
Supplier<Player> player;
|
||||
Supplier<Obstacle> obstacle;
|
||||
}
|
||||
|
||||
// Concrete factories:
|
||||
class KittiesAndPuzzles extends GameElementFactory {
|
||||
KittiesAndPuzzles() {
|
||||
player = Kitty::new;
|
||||
obstacle = Puzzle::new;
|
||||
}
|
||||
}
|
||||
|
||||
class KillAndDismember extends GameElementFactory {
|
||||
KillAndDismember() {
|
||||
player = KungFuGuy::new;
|
||||
obstacle = NastyWeapon::new;
|
||||
}
|
||||
}
|
||||
|
||||
public class GameEnvironment {
|
||||
private Player p;
|
||||
private Obstacle ob;
|
||||
|
||||
public GameEnvironment(GameElementFactory factory) {
|
||||
p = factory.player.get();
|
||||
ob = factory.obstacle.get();
|
||||
}
|
||||
public void play() {
|
||||
p.interactWith(ob);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
GameElementFactory kp = new KittiesAndPuzzles(), kd = new KillAndDismember();
|
||||
GameEnvironment g1 = new GameEnvironment(kp), g2 = new GameEnvironment(kd);
|
||||
g1.play();
|
||||
g2.play();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```java
|
||||
Kitty has encountered a Puzzle
|
||||
KungFuGuy now battles a NastyWeapon
|
||||
```
|
||||
|
||||
在这种环境中,**Player**对象与**Obstacle**对象进行交互,但是根据你所玩游戏的类型,存在不同类型的玩家和障碍物。 你可以通过选择特定的**GameElementFactory**来确定游戏的类型,然后**GameEnvironment**控制游戏的设置和玩法。 在此示例中,设置和玩法非常简单,但是这些活动(初始条件和状态变化)可以决定游戏的大部分结果。 这里,**GameEnvironment**不是为继承而设计的,尽管这样做很有意义。 它还包含“双重调度”和“工厂方法”的示例,稍后将对这两个示例进行说明。
|
||||
|
||||
<!-- Function Objects -->
|
||||
|
||||
## 函数对象
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user