函数式 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#