函数式 Java 中的现代策略模式

在我们目前在Evojam最大的Java项目中,我们已经调整了策略模式的配方。我们通过一小撮枚举和功能性句法糖来添加我们的个人风格。与往常一样,最简单的解释方法是使用示例。让我们直接进入它。

设置场景

拍摄照片时,曝光取决于三个值。如果您随机选择它们,则结果可能会比您预期的更明亮或模糊。幸运的是,即使是老式模拟相机也可以让您使用完全手动以外的模式。以下类表示拍照前所需的所有控件:

爪哇岛

@lombok.Valuebrclass CameraControls {brbr    Mode mode;brbr    FilmSpeed filmSpeed;br    Aperture aperture;br    Shutter shutter;br}

在模拟摄影中,速度(也称为ISO)取决于您使用的胶卷。然后,您的相机要么读取它,要么需要您进行设置。插入新卷筒后,执行此操作一次。

这给你留下了光圈和快门。我们在每种模式下以不同的方式处理它们:

爪哇岛

enum Mode {br    MANUAL, APERTURE_PRIORITYbr}

在手动模式下,光圈和快门速度都是用户所要求的。如果照片太暗或太亮,那不是相机的错。光圈优先级不同。顾名思义,它使用用户设置的光圈来选择正确的快门速度,同时考虑到特定的胶片速度。这是一种平衡行为,通常所说的“曝光三角形”最能说明这一点。


相机使用测光表在光圈优先模式下选择正确的快门速度。在外面阳光明媚的日子里,三角形的理想区域将与您的客厅不同。这就是为什么您需要为每个新场景调整控件以获得正确的曝光。

好吧,如果你不了解摄影,听起来都有点压倒性。不过,这并不复杂。每个控件都有一组有限的值:

爪哇岛

enum FilmSpeed {br    _100, _200, _400, _800br}brbrenum Aperture {br    F2, F4, F8, F16br}brbrenum Shutter {br    _60, _250, _500, _1000br}

当您按下快门释放时,胶片速度已经固定在卷轴上。这给我们留下了两个值来传递给shoot()方法:

爪哇岛

void shoot(Aperture aperture, Shutter shutter) {...}

保持简单

正如我之前所说,模式会影响光圈和快门值的处理。在手册中,相机从用户控件中获取值是理所当然的。这正是下面第一个if块中发生的事情。

光圈优先级表示您希望使用在控制中传递的光圈值。对于快门速度,请参阅测光表。您可以为它提供胶片速度和所需的光圈。作为回报,您将获得基于光测量的快门速度建议。您可以在第二个 if 块中遵循此逻辑。

爪哇岛

void shutterRelease(CameraControls controls, LightMeter meter) {brbr    if (controls.getMode() == Mode.MANUAL) {brbr        shoot(controls.getAperture(), controls.getShutter());brbr    } else if (controls.getMode() == Mode.APERTURE_PRIORITY) {brbr        Shutter shutter = meter.pickShutter(br                controls.getFilmSpeed(),br                controls.getAperture()br        );brbr        shoot(controls.getAperture(), shutter);br    }br}

一步太远

俗话说,软件中唯一不变的就是变化。Java 方法中的 if 语句可能不会长时间保持不变。更不用说一个方法中的两个 if 语句了。

在我们的例子中,一个可能的更改请求可能是处理新模式。快门优先是光圈优先的反面。让我们将其添加到原始枚举中:

爪哇岛

enum Mode {br    MANUAL, APERTURE_PRIORITY, SHUTTER_PRIORITYbr}

只添加第三个值是非常幼稚的,但代码会编译。在我们最初的设计中,没有新的模式出现。当摄影师选择快门优先级并按下快门释放时,没有任何反应。要处理新模式,您需要添加另一个 if 块。

但是,必须有更好的方法来处理这个问题。还有——它以策略模式的名义出现。

首先要做的是定义用于拾取快门和光圈值的接口。您可以使用通用类型 T 来表示快门或光圈:

爪哇岛

interface Picker<T> {br    T pick(CameraControls settings, LightMeter meter);br}

模式在光圈和快门的选择方式上有所不同。使用选取器策略参数化模式似乎是很自然的。在枚举中引入私有最终字段使它们成为必需且不可变的。这正是您将来每种现有模式以及任何新模式所需要的:

爪哇岛

@lombok.Getterbr@lombok.RequiredArgsConstructorbrenum Mode {brbr    MANUAL(),br    APERTURE_PRIORITY(),br    SHUTTER_PRIORITY();brbr    private final Picker<Aperture> aperturePicker;br    private final Picker<Shutter> shutterPicker;br}

要编译上面的代码,还需要将两个参数传递给每个构造函数。Picker 是一个功能接口,因为它只有一个方法。您可以使用简单的Java lambdas实现它:

爪哇岛

@lombok.Getterbr@lombok.RequiredArgsConstructorbrenum Mode {brbr    MANUAL(br            (controls, meter) -> controls.getAperture(),br            (controls, meter) -> controls.getShutter()br    ), brbr    ...br}

最后的润色

经过再三考虑,我们最终可能会重复自己。手动和光圈优先从用户控制中获取光圈。手动和快门优先均可从用户控制中获取快门速度。牢记 DRY 原则,让我们将这些 lambda 提取到静态方法中。

爪哇岛

interface Picker<T> {brbr    T pick(CameraControls settings, LightMeter meter);brbr    static Aperture apertureFixed(CameraControls controls, br                                  LightMeter meter) {br        return controls.getAperture();br    }brbr    static Shutter pickShutter(CameraControls controls, br                                    LightMeter meter) {br        return meter.pickShutter(br                controls.getFilmSpeed(), br                controls.getAperture()br        );br    }brbr    ...br}

你必须承认,最终结果是一段漂亮的干净代码:

爪哇岛

@lombok.Getterbr@lombok.RequiredArgsConstructorbrenum Mode {brbr    MANUAL(Picker::apertureFixed, Picker::shutterFixed),br    APERTURE_PRIORITY(Picker::apertureFixed, Picker::pickShutter),br    SHUTTER_PRIORITY(Picker::pickAperture, Picker::shutterFixed);brbr    private final Picker<Aperture> aperturePicker;br    private final Picker<Shutter> shutterPicker;br}

首先,由于仔细选择方法和字段名称,它是不言自明的。其次,它不会让你未来的自己在不处理两个选择器的情况下引入新模式。最后,它适合在代码的其他部分中优雅地使用,根本没有if语句:

爪哇岛

@lombok.Valuebrclass CameraControls {brbr    Mode mode;brbr    Speed speed;br    Aperture aperture;br    Shutter shutter;brbr    Aperture pickAperture(LightMeter meter) {br        return mode.getAperturePicker().pick(this, meter);br    }brbr    Shutter pickShutter(LightMeter meter) {br        return mode.getShutterPicker().pick(this, meter);br    }br}brbrvoid shutterRelease(CameraControls controls, LightMeter meter) {brbr    Aperture aperture = controls.pickAperture(meter);br    Shutter shutter = controls.pickShutter(meter);brbr    shoot(aperture, shutter);br}

确保将此代码与早期的 shutterRelease() 进行比较,后者具有两个 if 语句。哪一个更容易阅读?请记住,在这一点上,我们甚至没有处理第三种模式。对于另一种模式,我们需要另一个if块。

保持稳定

战略模式使我们的实施以多种方式实现。更改 Mode 枚举的唯一原因是处理新选择。使用新模式扩展枚举很容易,而无需修改 shutterRelease()。

继续自己找到所有五个SOLID原则的证据。请把它们放在下面的评论部分。一旦你这样做了,你应该开始注意到你当前项目中的完美用例。

#Java#
全部评论
学到了,感谢分享
点赞 回复 分享
发布于 2022-08-20 18:31 陕西

相关推荐

评论
点赞
收藏
分享
牛客网
牛客企业服务