本周的帖子很特别,因为它与面向对象编程有关。 如今,取消OOP颇为流行。 周围有很多困惑。 有些人将OOP与访问器 ( 即 getter和setter)或共享的可变状态 (或什至两者)混合。 这是不正确的,正如我们将在这篇文章中看到的那样。
这是7在编程风格焦点series.Other职位练习日交包括:
- 以编程风格介绍练习
- 以编程风格进行练习,将内容堆叠起来
- 编程风格的练习,Kwisatz Haderach风格
- 编程风格的练习,递归
- 具有高阶功能的编程风格的练习
- 以编程风格进行练习
- 以编程风格进行练习,回到面向对象编程 (本文)
- 编程风格的练习:地图也是对象
- 编程风格的练习:事件驱动的编程
- 编程风格的练习和事件总线
- 反思编程风格的练习
- 面向方面的编程风格的练习
- 编程风格的练习:FP&I / O
- 关系数据库风格的练习
- 编程风格的练习:电子表格
- 并发编程风格的练习
- 编程风格的练习:在线程之间共享数据
- 使用Hazelcast以编程风格进行练习
- MapReduce样式的练习
- 编程风格的练习总结
关于OOP的简短提醒
OOP的宗旨是将系统建模为映射现实世界的对象。 但是,出现OOP的原因之一是对先前使用的全局变量-共享的可变状态做出了反应。 全局变量可以在程序中的任何位置进行访问和设置。 因此,很容易引入错误。
OOP原则有两个方面:
- 特定数据的处理和存储应位于同一对象中- 封装
- 对象通过可能包含数据的消息彼此通信...
这些原则不是关于可变性的,也不是吸气剂/设定器! 虽然我在实践中表示同意,但是某些语言/框架/做法使用可变性和访问器,但没有什么可以阻止您这样做。 这仍然是OOP代码-也许,只有通过删除它们,您才能进行真正的OOP。
对系统建模
原始的Python代码提供以下模型,该模型直接映射到类:
类 | 职责范围 |
---|---|
|
|
|
|
|
|
|
管理和排序先前对象之间的消息流 |
使用类型系统改进初始设计
原始的Python代码调度String
消息,这很容易出错,并且不便于重构。 为了从类型系统中受益,可以通过Message
标记器接口来实现调度。
interfaceMessage
在某些情况下,需要其他信息。 为此,新的抽象PayloadMessage<T>
类实现Message
,并提供接收者可以使用的T
类型的有效负载。
abstractclassPayloadMessage<T>:Message{
abstractvalpayload:T
}
这导致以下设计:
对于示例应用程序来说,这绰绰有余。 对于更复杂的系统,可以考虑提供其他类来容纳一个以上的有效负载参数, 例如 PayloadMessage2<T, V>
, PayloadMessage3<T, V, X>
等。相反,OOP设计可以创建一个实际的概念来围绕所有参数。 例如,要创建一个Person
,可能要发送名字,姓氏和生日:代替PayloadMessage3<String, String, LocalDate>
类,实际上可以重用Person
抽象,或者更好的是,创建一个专用的PersonMessage(String, String, LocalDate)
。
该应用程序的流程如下,只需使用Kotlin即可实现:
权衡类型安全性以便将来更轻松地进行更改
这里的核心功能是dispatch()
函数。 通常,类通过其功能提供非常严格的API。
为了引入更多的灵活性,创建一个接口,然后可以更自由地更改实现。 不幸的是,这使得一次性设计界面功能成为可能。 设计完成后,除了添加新功能之外的任何更改都将成为一项重大更改。
具有更通用的dispatch()
函数可提供更多选择。 不幸的是,这是有代价的。 调用代码利用dispatch()
方法的返回值。 不幸的是,它的返回类型是Any?
类型Any?
,因为它需要在所有可能的情况下使用。
interfaceMessageDispatch{
fundispatch(message:Message):Any?
}
因此,调用代码每次都需要转换返回值,从而降低类型安全性。
给我看看代码!
这是上述类别之一的示例:
classDataStorageManager:MessageDispatch{ (1)
classWordsMessage:Message (2)
classInitMessage(overridevalpayload:String):PayloadMessage<String>() (2)
privatelateinitvarfilename:String
privatevalwords:List<String>bylazy{
read(filename)
.flatMap{it.split("\\W|_".toRegex())}
.filter{it.isNotBlank()&&it.length>=2}
.map(String::toLowerCase)
}
overridefundispatch(message:Message)=when(message){ (3)
isInitMessage->filename=message.payload
isWordsMessage->words
else->throwException("Uknown message $message")
}
}
- 所有类都实现
MessageDispatch
- 类提供他们能够在
dispatch()
实现中处理的消息 -
dispatch()
函数是类的单个入口点
结论
在本练习中,我们无需使用访问器或共享可变状态就可以使用OOP。 因此,永远不要将它们混为一谈。
同样,设计OOP类也是一个权衡的问题。 类型是在编译时捕获可能的错误的好方法。 但是,这种安全性是有代价的:它使最初的设计成为一场赌博,因为人们通常不知道将来需要进行哪些更改。 具有单个入口点功能会降低返回类型的安全性,但允许更多更改是不间断的。