构造函数还是静态工厂方法?

我相信Joshua Bloch在他的非常好的书“ Effective Java”中首先说了它:与构造函数相比,静态工厂方法是实例化对象的首选方法。 我不同意。 不仅因为我相信静态方法是纯粹的邪恶,而且主要是因为在这种特殊情况下,它们伪装成好的方法,使我们认为我们必须爱上它们。

摘录(2009),作者:麦克·贾奇(Mike Judge)

让我们从面向对象的角度分析推理并查看其原因。

这是一个具有一个主要构造函数和两个次要构造函数的类:

class Color {
  private final int hex;
  Color(String rgb) {
    this(Integer.parseInt(rgb, 16));
  }
  Color(int red, int green, int blue) {
    this(red << 16 + green << 8 + blue);
  }
  Color(int h) {
    this.hex = h;
  }
}

这是带有三个静态工厂方法的类似类:

class Color {
  private final int hex;
  static Color makeFromRGB(String rgb) {
    return new Color(Integer.parseInt(rgb, 16));
  }
  static Color makeFromPalette(int red, int green, int blue) {
    return new Color(red << 16 + green << 8 + blue);
  }
  static Color makeFromHex(int h) {
    return new Color(h);
  }
  private Color(int h) {
    return new Color(h);
  }
}

你更喜欢哪一个?

据约书亚布洛赫,但使用静态工厂方法而不是构造函数(一共设置了四个,但第四个是不是适用于Java的三个基本优势 ):

  • 他们有名字。
  • 他们可以缓存。
  • 它们可以是子类型。

我认为,如果设计错误,那么这三者都是完全合理的。 它们是解决方法的好借口。 让我们一一介绍。

他们有名字

这是使用构造函数制作红色番茄颜色对象的方法:

Color tomato = new Color(255, 99, 71);

这是使用静态工厂方法执行的操作:

Color tomato = Color.makeFromPalette(255, 99, 71);

看起来makeFromPalette()在语义上比new Color()更丰富,对吗? 嗯,是。 如果我们将它们传递给构造函数,谁知道这三个数字意味着什么。 但是“调色板”一词可以帮助我们立即解决所有问题。

真正。

但是,正确的解决方案是使用多态和封装,以将问题分解为几个语义丰富的类:

interface Color {
}
class HexColor implements Color {
  private final int hex;
  HexColor(int h) {
    this.hex = h;
  }
}
class RGBColor implements Color {
  private final Color origin;
  RGBColor(int red, int green, int blue) {
    this.origin = new HexColor(
      red << 16 + green << 8 + blue
    );
  }
}

现在,我们使用正确的类的正确的构造函数:

Color tomato = new RGBColor(255, 99, 71);

看,约书亚?

他们可以缓存

假设我在应用程序中的多个位置需要一个红色的番茄色:

Color tomato = new Color(255, 99, 71);
// ... sometime later
Color red = new Color(255, 99, 71);

将创建两个对象,这显然是低效的,因为它们是相同的。 最好将第一个实例保留在内存中的某个位置,并在第二个调用到达时将其返回。 静态工厂方法可以解决这个问题:

Color tomato = Color.makeFromPalette(255, 99, 71);
// ... sometime later
Color red = Color.makeFromPalette(255, 99, 71);

然后,在Color内的某个地方,我们保留了一个私有静态Map ,其中已实例化了所有对象:

class Color {
  private static final Map<Integer, Color> CACHE =
    new HashMap<>();
  private final int hex;
  static Color makeFromPalette(int red, int green, int blue) {
    final int hex = red << 16 + green << 8 + blue;
    return Color.CACHE.computeIfAbsent(
      hex, h -> new Color(h)
    );
  }
  private Color(int h) {
    return new Color(h);
  }
}

这是非常有效的性能。 对于像我们的Color这样的小对象,问题可能并不那么明显,但是当对象较大时,其实例化和垃圾回收可能会浪费大量时间。

真正。

但是,有一种面向对象的方法可以解决此问题。 我们只是介绍了一个新类Palette ,它变成了一个颜色存储区:

class Palette {
  private final Map<Integer, Color> colors =
    new HashMap<>();
  Color take(int red, int green, int blue) {
    final int hex = red << 16 + green << 8 + blue;
    return this.computerIfAbsent(
      hex, h -> new Color(h)
    );
  }
}

现在,我们一次创建一个Palette实例,并要求它在每次需要时向我们返回一种颜色:

Color tomato = palette.take(255, 99, 71);
// Later we will get the same instance:
Color red = palette.take(255, 99, 71);

见,约书亚,没有静态方法,没有静态属性。

他们可以亚型

假设我们的Color类有一个lighter()方法,该方法应该将颜色转移到下一个可用的打火机上:

class Color {
  protected final int hex;
  Color(int h) {
    this.hex = h;
  }
  public Color lighter() {
    return new Color(hex + 0x111);
  }
}

但是,有时更希望通过一组可用的Pantone颜色选择下一种较浅的颜色:

class PantoneColor extends Color {
  private final PantoneName pantone;
  PantoneColor(String name) {
    this(new PantoneName(name));
  }
  PantoneColor(PantoneName name) {
    this.pantone = name;
  }
  @Override
  public Color lighter() {
    return new PantoneColor(this.pantone.up());
  }
}

然后,我们创建一个静态工厂方法,该方法将决定哪种Color实现最适合我们:

class Color {
  private final String code;
  static Color make(int h) {
    if (h == 0xBF1932) {
      return new PantoneColor("19-1664 TPX");
    }
    return new RGBColor(h);
  }
}

如果要求使用真正的红色 ,我们将返回PantoneColor一个实例。 在其他所有情况下,它只是一个标准的RGBColor 。 该决定是通过静态工厂方法做出的。 这就是我们所说的:

Color color = Color.make(0xBF1932);

由于构造函数只能返回在其中声明的类,因此不可能对构造函数进行相同的“分叉”。静态方法具有返回Color任何子类型的所有必要自由。

真正。

但是,在面向对象的世界中,我们可以而且必须以不同的方式去做。 首先,我们将Color为接口:

interface Color {
  Color lighter();
}

接下来,我们将将此决策过程移至其自己的类Colors ,就像在上一个示例中所做的那样:

class Colors {
  Color make(int h) {
    if (h == 0xBF1932) {
      return new PantoneColor("19-1664-TPX");
    }
    return new RGBColor(h);
  }
}

而且我们将使用Colors类的实例,而不是Color内部的静态方法:

colors.make(0xBF1932);

但是,这仍然不是真正的面向对象的思维方式,因为我们正在将决策权从对象所属的对象转移开。 通过静态工厂方法make()或新类Colors实际上并不重要),我们将对象分成两部分。 第一部分是对象本身,第二部分是决策算法,它位于其他地方。

更加面向对象的设计是将逻辑放入PantoneColor类的对象中,该对象将装饰原始的RGBColor

class PantoneColor {
  private final Color origin;
  PantoneColor(Color color) {
    this.origin = color;
  }
  @Override
  public Color lighter() {
    final Color next;
    if (this.origin.hex() == 0xBF1932) {
      next = new RGBColor(0xD12631);
    } else {
      next = this.origin.lighter();
    }
    return new PantoneColor(next);
  }
)

然后,我们创建一个RGBColor实例,并使用PantoneColor装饰它:

Color red = new PantoneColor(
  new RGBColor(0xBF1932)
);

我们要求red返回较浅的颜色,它返回Pantone调色板中的一种,而不是仅在RGB坐标中较浅的颜色:

Color lighter = red.lighter(); // 0xD12631

当然,这个示例是原始的,如果我们真的希望它适用于所有Pantone颜色,则需要进一步改进 ,但是我希望您能理解。 逻辑必须保留内部 ,而不是外部,静态工厂方法甚至其他补充类中。 当然,我在说的是属于这个特定类的逻辑。 如果与类实例的管理有关,那么可以有容器和存储,就像上面的上一个示例一样。

总而言之,我强烈建议您不要使用静态方法,尤其是当它们要替换对象构造函数时。 通过其构造函数生成对象是任何面向对象软件中 “神圣”的时刻,请不要错过它的美丽。

您可能还会发现这些相关的帖子很有趣: 每个私有静态方法都是新类的候选人您是一个更好的建筑师,您的图表更简单只有一个主要的建设者 ; 为什么InputStream设计错误 ; 为什么在OOP中很多退货声明是个坏主意 ;

翻译自: https://www.javacodegeeks.com/2017/11/constructors-static-factory-methods.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值