以编程风格进行练习,回到面向对象的编程

本周的帖子很特别,因为它与面向对象编程有关。 如今,取消OOP颇为流行。 周围有很多困惑。 有些人将OOP与访问器 getter和setter)或共享的可变状态 (或什至两者)混合。 这是不正确的,正如我们将在这篇文章中看到的那样。

这是7在编程风格焦点series.Other职位练习交包括:

  1. 以编程风格介绍练习
  2. 以编程风格进行练习,将内容堆叠起来
  3. 编程风格的练习,Kwisatz Haderach风格
  4. 编程风格的练习,递归
  5. 具有高阶功能的编程风格的练习
  6. 以编程风格进行练习
  7. 以编程风格进行练习,回到面向对象编程 (本文)
  8. 编程风格的练习:地图也是对象
  9. 编程风格的练习:事件驱动的编程
  10. 编程风格的练习和事件总线
  11. 反思编程风格的练习
  12. 面向方面的编程风格的练习
  13. 编程风格的练习:FP&I / O
  14. 关系数据库风格的练习
  15. 编程风格的练习:电子表格
  16. 并发编程风格的练习
  17. 编程风格的练习:在线程之间共享数据
  18. 使用Hazelcast以编程风格进行练习
  19. MapReduce样式的练习
  20. 编程风格的练习总结

关于OOP的简短提醒

OOP的宗旨是将系统建模为映射现实世界的对象。 但是,出现OOP的原因之一是对先前使用的全局变量-共享的可变状态做出了反应。 全局变量可以在程序中的任何位置进行访问和设置。 因此,很容易引入错误。

OOP原则有两个方面:

  • 特定数据的处理和存储应位于同一对象中- 封装
  • 对象通过可能包含数据的消息彼此通信...

这些原则不是关于可变性的,也不是吸气剂/设定器! 虽然我在实践中表示同意,但是某些语言/框架/做法使用可变性和访问器,但没有什么可以阻止您这样做。 这仍然是OOP代码-也许,只有通过删除它们,您才能进行真正的OOP。

对系统建模

原始的Python代码提供以下模型,该模型直接映射到类:

职责范围

DataStorage

  • 读取文字档
  • 将内容解析为文字
  • 储存文字
  • 回话

StopWordManager

  • 读取停用词文件
  • 解析其内容
  • 存储停用词
  • 检查单词是否是停用词

WordFrequencyManager

  • 存储字频图
  • 管理单词提交的频率
    • 如果该词尚不存在,请以1的频率添加
    • 如果是这样,将其频率增加一

WordFrequencyController

管理和排序先前对象之间的消息流

使用类型系统改进初始设计

原始的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")
    }
}
  1. 所有类都实现MessageDispatch
  2. 类提供他们能够在dispatch()实现中处理的消息
  3. dispatch()函数是类的单个入口点

结论

在本练习中,我们无需使用访问器或共享可变状态就可以使用OOP。 因此,永远不要将它们混为一谈。

同样,设计OOP类也是一个权衡的问题。 类型是在编译时捕获可能的错误的好方法。 但是,这种安全性是有代价的:它使最初的设计成为一场赌博,因为人们通常不知道将来需要进行哪些更改。 具有单个入口点功能会降低返回类型的安全性,但允许更多更改是不间断的。

这篇文章的完整源代码可以在Github上找到。

翻译自: https://blog.frankel.ch/exercises-programming-style/7/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值