kotlin入门
7.公开方法作为API合同 (7. Public Methods as API Contracts)
背景:(Background:)
Direct concrete object interaction with its encapsulated public methods is considered a bad design. A good object should always work by contracts¹²; this simple mantra. But if one is still highly skeptical with this non-trivial mantra, at least they need to understand that it’s under-backed by 2 big names, EO and SOLID.
直接的具体对象与其封装的公共方法进行交互被认为是错误的设计。 一件好的物品应始终按照合同1,2来运作; 这个简单的口头禅。 但是,如果仍然对这一非凡的口头禅持高度怀疑的话,至少他们需要了解,它并没有得到EO和SOLID这两个大牌的支持。
There are at least 2 practical reasons why you want to satisfy ME, the Maintainability & Extensibility factors. Given a design:
还有你为什么要满足我至少2个实际的原因,将M aintainability&E xtensibility因素。 给定设计:
// Concreter
class TextRandom(val random: RandomGenerator) {
fun generate(length: Int): String {
return random.generate(length).toString() // assume: hello_world😜
}
}
/**
* Declared [textRandom] as Dependency
*/
fun print(textRandom: TextRandom) {
println(textRandom.generate(length = 12)) // hello_world😜
}
// Somewhere layer where the "print" method invoked with its concrete DI
print(textRandom = TextRandom(random = RandomGenerator))
Regarding the above naive design, assume:
关于以上天真的设计,假定:
(M) There are hundreds of
TextRandom
declarations scattered over A to Z modules. Somehow theTextRandom
is decided to be renamed (like for real!?). If this is the Android platform that we’re under developing, is it worthed for the recompiling penalty by those impacted modules because of such a trivial change?(M)在A到Z模块上散布着数百个
TextRandom
声明。 不知何故,决定将TextRandom
重命名(就像真实的一样!?)。 如果这是我们正在开发的Android平台,那么由于如此微不足道的更改,值得那些受影响的模块重新编译吗?
Bonus: Be prepared as well to request each module owner approval. 😭
奖励:还准备好请求每个模块所有者的批准。 😭
(M) While in UT, how would one be able to represent the fake version of the
TextRandom
object? Well, correct me but it’s impossible except relying on “magics” from some capable mock libraries which could probably turn to unreliable¹³ and unmaintainable¹⁴ testing.(M)在UT中,如何能够代表
TextRandom
对象的伪造版本? 好吧,纠正我,但是除了依靠某些功能强大的模拟库中的“魔术”,这不可能,而这可能会导致不可靠的13测试和不可维护的1测试。
Bonus: Like the old saying, there will always be a price for a hidden “magic”. So try to adapt if each test should take a little bit longer to complete. 😭
奖励:就像俗话所说的那样,隐藏的“魔术”总会有代价。 因此,如果每个测试都需要更长的时间才能完成,请尝试调整。 😭
(E) Among A to Z modules, X has a new requirement that any special character should be removed from the generated text and it should be capitalized as well. So how would one add these functionalities by preserving the old
generate
implementation so the other modules can still have the original behavior benefit?(E)在A到Z模块中, X有一个新要求,即应从生成的文本中删除任何特殊字符,并且也应将其大写。 那么,如何通过保留旧的
generate
实施方式添加这些功能,以便其他模块仍然可以拥有原始的行为优势?
减轻: (Mitigation:)
The idea is simple. Create a contract with those public signature methods as part of the APIs and have the Concreter
implements its counterpart based on the contract which results in high decoupling code.
这个想法很简单。 使用这些公开签名方法作为API的一部分来创建合同,并让Concreter
根据该合同实现对等方,从而产生较高的去耦代码。
Thanks to this abstraction, this benefits us by protecting the high-level modules (Client
) from any further implementation change by the lower one (Dependency
). Besides, we too can create the fake version of the dependency naturally without any “magic” aid in UT’s context.
多亏了这种抽象,通过保护高级模块( Client
)免受下级模块( Dependency
)的任何进一步实现更改,这使我们受益。 此外,我们也可以自然地创建依赖关系的伪造版本,而无需在UT的上下文中提供任何“魔术”帮助。
interface TextGenerator {
// API
fun generate(length: Int): String
}
// Concreter
class TextRandom(val random: RandomGenerator) : TextGenerator {
override fun generate(length: Int): String {
return random.generate(length).toString() // assume: hello_world😜
}
}
/**
* Client
*
* Declared [textGenerator] as Dependency
*/
fun print(textGenerator: TextGenerator) {
println(textGenerator.generate(length = 12)) // hello_world😜
}
// Somewhere layer where the "print" method invoked with its inverted DI
print(textRandom = TextRandom(random = RandomGenerator))
Meanwhile, here is the suggested design for the text decorating requirement will look like:
同时,这是文本修饰要求的建议设计,如下所示:
interface TextGenerator {
// API
fun generate(length: Int): String
}
// Concreter
class TextRandom(val random: RandomGenerator) : TextGenerator {
override fun generate(length: Int): String {
return random.generate(length).toString() // assume: hello_world😜
}
}
// Concreter
class TextWithNoSpecialCharacter(val original: TextGenerator) : TextGenerator {
override fun generate(length: Int): String {
val regex = Regex(pattern = "[^A-Za-z0-9 ]")
return regex.replace(input = original.generate(length), replacement = "")
}
}
// Concreter
class TextWithCapitalized(val original: TextGenerator) : TextGenerator {
override fun generate(length: Int): String {
return original.generate(length).capitalize()
}
}
/**
* Client in module X
*
* Declared [textGenerator] as Dependency
*/
fun print(textGenerator: TextGenerator) {
println(textGenerator.generate(length = 12)) // Helloworld
}
// Somewhere layer where the "print" method in module X invoked with its inverted DI
print(
textGenerator =
TextWithCapitalized(
original = TextWithNoSpecialCharacter(
original = TextRandom(random = RandomGenerator)
)
)
)
Thanks with the equipped contract interface
, now we can apply the “decorator pattern¹⁵” for the new TextWithNoSpecialCharacter
and TextWithCapitalized
features.
借助配备的合同interface
,现在我们可以为新的TextWithNoSpecialCharacter
和TextWithCapitalized
功能应用“装饰器样式¹⁵”。
Check out how we construct the requirement in a composed and declarative fashion. By this great extensible foundation, now we welcome every new text decorator feature with zero extensibility worries in the future. Remember, the more (useful) abstraction we add, the more flexibility we earn.
查看我们如何以一种组合式和声明式的方式构造需求。 通过这个强大的可扩展基础,现在我们欢迎将来使用零扩展性担忧的每一个新的文本装饰器功能。 请记住,我们添加的(有用的)抽象越多,我们获得的灵活性就越大。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/b2b5b440ad262a4be125c6aceffd81ab.png)
Note:
注意:
1. The decorator is my favorite among all other patterns I’m aware of. It is a very simple but very powerful mechanism to respect the SO¹⁶LID in very well manner.
1.装饰器是我所知道的所有其他模式中的最爱。 这是一个很简单但很强大的机制,尊重在S Ø ¹⁶LID 很好地
2. Some might argue with the decorator pattern vs extension method topic. Please note that it’s not about adding new methods. It’s about adding new functionality to existing methods.
2.有些人可能会反对装饰器模式与扩展方法主题。 请注意,这与添加新方法无关。 这是关于向现有方法添加新功能。
Decorator is actually defined as using wrapping classes concept which will allow us to alter behaviour dynamically at runtime -- something we couldn’t do with the compiled static extension method.
实际上,Decorator被定义为使用包装类概念,这将使我们能够在运行时动态更改行为-使用编译的静态扩展方法无法做到这一点。
8.“移民”单身人士 (8. The “Immigrant” Singleton)
a.k.a. Singleton inside a method. No doubt but my bread and butter view in some major code review activities. Consider a design:
亦称方法内的Singleton。 毫无疑问,但是在一些主要的代码审查活动中,我的观点是坚定的。 考虑一个设计:
object DateUtils {
fun getDate(millis: Long): Date {
return Date(millis)
}
fun isAlreadyPast(fromDate: Date, date: Date): Boolean {
return fromDate.after(date)
}
}
class Presenter {
// ...
private fun isSomethingEligibleToExecute(millis1: Long, millis2: Long) {
val date1 = DateUtils.getDate(millis = millis1)
val date2 = DateUtils.getDate(millis = millis2)
return DateUtils.isAlreadyPast(fromDate = date1, date = date2)
}
}
*Typical me every time reading after:
*代表我每次阅读以下内容:
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/ea875806589e52c493025af15a69b836.png)
背景:(Background:)
The problem is not with the Singleton per se. It usually shines under circumstances, memory usage for instance.
问题不在于Singleton本身。 通常情况下会发光,例如内存使用情况。
There we can see the Presenter
is tightly coupled with the Singleton which means the code is dead-end, error-prone¹⁷, and with the all bad WHYs issued at the previous factor.
在那里,我们可以看到Presenter
与Singleton紧密耦合,这意味着代码是死胡同,容易出错,并且具有前一个因素发出的所有不良WHY 。
减轻: (Mitigation:)
interface DateManager {
fun convertToDate(millis: Long): Date
fun isAlreadyPast(fromDate: Date, date: Date): Boolean
}
object DateUtils : DateManager {
override fun convertToDate(millis: Long): Date {
return Date(millis)
}
override fun isAlreadyPast(fromDate: Date, date: Date): Boolean {
return fromDate.after(date)
}
}
class Presenter(dateManager: DateManager) {
// ...
private fun isSomethingEligibleToExecute(millis1: Long, millis2: Long) {
val date1 = dateManager.convertToDate(millis = millis1)
val date2 = dateManager.convertToDate(millis = millis2)
return dateManager.isAlreadyPast(fromDate = date1, date = date2)
}
}
With hidden DateUtils
details over a contract and strategic injectable position, this comes with at least 2 benefits:
在合同和战略可注射位置上具有隐藏的DateUtils
详细信息,这至少具有两个好处:
The loosely-coupled design which resulting reusable
Presenter
codes with flexible/switchable behavior ofdateManager
dependency in the latter,松散耦合的设计产生了可重用的
Presenter
代码,并在后者中具有dateManager
依赖项的灵活/可切换行为,Faking out the
dateManager
dependency is much easier on the testing side.在测试方面,伪造
dateManager
依赖关系要容易得多。
9.构造函数应无代码 (9. Constructors Should Be Code-Free)
Are you coding in imperative or declarative style? If an imperative adopter, then this factor might oppose the belief, or if you think it’s going to be hard to follow this factor part, you may skip. Else, now we are going to learn how a constructor injected with some side-effects is a bad practice.
您使用命令式还是声明式编码? 如果是当务之急的采用者,那么此因素可能会与信念背道而驰,或者如果您认为很难遵循该因素,则可以跳过。 否则,现在我们将学习注入一些副作用的构造函数是一种不好的做法。
背景: (Background:)
In declarative programming, we try to delay for as long as possible any calculation which introduces to a side-effect, until it is requested by the object owner (client). Now, here comes a constructor with 99% responsibility is done within:
在声明式编程中,我们尝试尽可能延迟任何会带来副作用的计算,直到对象所有者(客户端)请求它为止。 现在,在这里完成了具有99%责任的构造函数:
class FileReader(fileName: String) {
private var content: String? = null
init {
try {
content = File(fileName).readText()
} catch (cause: IOException) {}
}
fun readContent(): String? = content
}
Alright, now I’ll try my best to dictate what possible reason lies over the design (assume the file is read-only):
好了,现在我将尽力指出设计的可能原因(假设文件是只读的):
“Why should I re-read the content from a given
fileName
when I can have it done once enough? ThereadContent
will then need to return same content result every time it is requested which will resolve time performance issue. Constructorinit
block should be a perfect site for that.”“当我可以一次完成一次
fileName
时,为什么要重新读取它的内容? 然后,每次请求readContent
都需要返回相同的内容结果,这将解决时间性能问题。 构造函数init
块应该是一个理想的选择。”
Now the side-effects afterward:
现在的副作用是:
- The class is dead-end, preventing against from any advanced behavior extension/composition of its object, 该类是死胡同的,可以防止对其对象进行任何高级行为扩展/组合,
- One didn’t consider if somewhere APIs will try to delete the corresponding file. The above design will keep returning consistent content although the file has just been deleted. 人们没有考虑过某个地方的API是否会尝试删除相应的文件。 尽管文件刚刚被删除,但是上述设计将继续返回一致的内容。
减轻: (Mitigation:)
We know why methods exist for. Try to atomize and delegate the behaviors/responsibilities to them. We don’t want to get charged extra penalty time every object instantiation because its constructor contains some relatively complex logic which is supposed only to create an instance from it or some local value mapping assignments.
我们知道为什么存在方法。 尝试将行为/责任细化并委派给他们。 我们不想在每个对象实例化时都花额外的时间,因为它的构造函数包含一些相对复杂的逻辑,这些逻辑只能从该实例创建实例或某些局部值映射分配。
If we have the same concern time performance as thought above, we could create another composition FileReaderCached
version to store the operating result by leveraging some APIs facilitated by each programming language (Cacheable
annotation in Java/by lazy
delegation in Kotlin). A sum up,
如果我们具有与上述相同的关注时间性能,则可以通过利用每种编程语言(Java中的Cacheable
批注/ Kotlin中by lazy
委派)提供便利的一些API,创建另一个组合FileReaderCached
版本来存储操作结果。 总结一下
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/d275cb917d9ef56b55ec3faab35dc8ff.png)
10.类是最终的还是抽象的(10. A Class is Either Final or Abstract)
A bold statement from “Seven Virtues of a Good Object¹²” where I really felt into EO’s gang at that time.
“好的物件的七个美德”²中的一个大胆的声明使我真正感受到了EO的帮派。
The main idea of this statement is there shouldn’t exist any inheritance material, like open
modifier in Kotlin because inheritance is “evil¹⁸”. Why? Consider a black box (final
) class:
该声明的主要思想是不应存在任何继承材料,例如Kotlin中的open
修饰符,因为继承是“邪恶的” 。 为什么? 考虑一个黑盒( final
)类:
class TextRandom(val random: RandomGenerator) {
/**
* ...
*
* Some internal methods utilize the "generate" method.
*/
fun generate(length: Int): String {
return random.generate(length).toString()
}
}
The black box looks fine. But assume a new requirement in which the generated random text should contain number only (not limited to). So one naively proposes the original generate
behavior can just be extended with some necessary logic. Hence, they extend the entire black box class to satisfy the come-up plan:
黑匣子看起来不错。 但是,假设有一个新的要求,其中生成的随机文本应仅包含数字(不限于此)。 因此,一个天真地提出原始generate
行为可以用一些必要的逻辑进行扩展。 因此,他们扩展了整个黑匣子类,以满足后续计划:
open class TextRandom(val random: RandomGenerator) {
/**
* ...
*
* Some internal methods utilize the "generate" method.
*/
open fun generate(length: Int): String {
return random.generate(length).toString()
}
}
class TextNumericRandom(random: RandomGenerator) : TextRandom(random) {
override fun generate(length: Int): String {
val text = super.generate(length)
return text.replace("[^0-9]".toRegex(), "")
}
}
Now the requirement is completed but the maintainability is at risk. We have no idea what could go wrong afterward. Please note that once we override the generate
method with a new piece of implementation in the child class, we risk breaking the logic of the entire parent class since all the internal methods start using this new generate
version.
现在,要求已完成,但可维护性受到威胁。 我们不知道以后会出什么问题。 请注意,一旦在子类中使用新的实现覆盖了generate
方法,由于所有内部方法都开始使用此新的generate
版本,因此冒着破坏整个父类逻辑的风险。
FAQ:
常问问题:
So how about the abstract class? Isn’t it leveraging the concept of inheritance too?
那么抽象类呢? 它不是也利用继承的概念吗?
Correct, but only into the places the class allows us to touch, in which explicitly marked abstract
methods. The class is “broken” and ready for any derivation. Some of its methods are not functioning which is expecting us to inject some implementation by overriding them. Nothing more than, the class defends its fixed behaviors (no marked open
in Kotlin) from getting any advanced extension.
正确,但仅在该类允许我们触摸的地方使用,其中明确标记了abstract
方法。 该课程“已中断”,可以进行任何派生。 它的某些方法无法正常运行,因此我们希望通过覆盖它们来注入一些实现。 仅此而已,该类捍卫其固定行为(在Kotlin中未标记为open
状态)以防止获得任何高级扩展。
减轻: (Mitigation:)
Any come-up class extension idea should be done isolated, zeroing any harmful potential behavior break of an entire concrete parent class. Thus, always prefer the “Composition over inheritance¹⁹” mantra with “decorator pattern¹⁵” as one of our tools.
任何即将上课的类扩展想法都应孤立完成,以使整个具体父类的任何有害的潜在行为中断都归零。 因此,我们始终偏向于将“组成比继承¹”的咒语与“装饰器样式¹”作为我们的工具之一。
Fact: Even worse scenario, one inherits an entire class because they just want to reuse some boilerplates. At that time, he/she has just about:
事实:更糟糕的情况是,一个人继承了整个类,因为他们只想重用一些样板。 当时,他/她差不多:
- Turning their working-class into what is called the “God Class”,
-将工人阶级变成所谓的“上帝阶级”,
- Going to offend the S⁷OL²⁰ID at some point,
- Breaking encapsulation -- knowing what it isn’t supposed to.
-破坏封装-知道不应做的事情。
11.时间耦合 (11. The Temporal Coupling)
As the saying goes, “Save the best for last”.
俗话说:“把最好的留到最后”。
IMO this last factor is a symptom, a time bomb, hides the most horrible design smell potential, more horrible than the classic NPE, yet, some developers today tend to don’t see that coming (at least based on my observation).
IMO的最后一个因素是症状,一颗定时炸弹,隐藏了最可怕的设计气味,比经典的NPE更可怕,但是,今天,一些开发人员倾向于看不到这种气味(至少基于我的观察)。
![Image for post](https://i-blog.csdnimg.cn/blog_migrate/b1b42006ca8f37ec75b27ba3524d8de4.png)
背景:(Background:)
Usually found in imperative programming style, temporal coupling happens when some sequential methods call must stay in a particular order (coupled), but the knowledge about that order is hidden and must be remembered precisely by the one who codes.
通常以命令式编程方式找到,当某些顺序方法调用必须保持特定顺序(耦合)时会发生时间耦合,但是有关该顺序的知识是隐藏的,必须由编写代码的人精确记住。
The order is also easy to break, typically when in refactoring, and the compiler won’t catch it for us. Hence, that usually produces day and night efforts spent on fixing some found “strange” bugs.
该顺序也很容易打破,通常是在重构时,并且编译器不会为我们抓住它。 因此,这通常会花费日夜的精力来修复一些已发现的“奇怪”错误。
The following snippet to visualize the temporal coupling issue in the simplest form:
以下代码片段以最简单的形式可视化时间耦合问题:
fun main() {
val circle = Circle()
circle.radius = 5
circle.calculateArea()
}
We can see at line 4 is temporal coupled with line 3. Try removing the line 3 will not trigger any error compilation and calculating the area before the required radius is settled will throw an exception (or return a default result, depend on implementation). This is what we called the hidden knowledge.
我们可以看到第4行与第3行是时间耦合的。尝试删除第3行不会触发任何错误编译,并且在确定所需半径之前计算面积会引发异常(或返回默认结果,具体取决于实现)。 这就是我们所说的隐藏知识。
减轻: (Mitigation:)
Numerous design patterns could solve such issue, constructor injection as one of. With the params injection at the moment of object creation, we ensure the radius
is always visible before any further dependent operation is requested. This shall eliminate the need to call two methods in sequence.
众多的设计模式可以解决这种问题,构造函数注入就是其中之一。 使用对象创建时的参数注入,我们确保在请求任何其他相关操作之前, radius
始终可见。 这样就无需依次调用两个方法。
IMO, still, that kind of solution is nothing more than a temporal workaround. We should come forward instead with effective mitigation to prevent similar issues continue occurring in our working codebase. A big picture of mine would be if only in this object-oriented world is fitted with more good objects; immutable objects, I believe this temporal coupling issue shouldn’t be about to exist.
IMO仍然认为,这种解决方案不过是暂时的解决方法。 相反,我们应该提出有效的缓解措施,以防止在我们的工作代码库中继续发生类似的问题。 如果仅在这个面向对象的世界中安装更多的好东西,我的概貌就是如此。 不变的对象,我相信这个时间耦合问题不应该存在。
Note:
注意:
The immutable object is one of the very important factors I wish to bring in this 2nd part. But due to it’s never been short, I think it deserves to have its own part.But if you’re eager to find how it became a thing, please refer to this great article²¹ and this²² as well for why we should prefer immutability over mutability now. That reading probably will also change our bias on how we see and treat an object before.
不可变对象是我希望在第二部分中介绍的非常重要的因素之一。 但由于它从来不缺,我认为它值得拥有自己的part.But如果急于找到它是如何成为一个东西,请参阅本伟大的文章²¹和这个²²以及为什么我们应该喜欢不变性现在超过可变性。 这种阅读可能也会改变我们对以前如何看待和对待物体的偏见。
闭幕 (Closing)
That’s it. That’s all about my compilation code review factors, for now. Part 3 is on the road-map, and those who seek my approval signal, better validate if the changes violate one of these factors.
而已。 到目前为止,这就是我的编译代码审查因素。 第三部分在路线图上,寻求我批准的人可以更好地验证这些更改是否违反了这些因素之一。
And of course, these factors are just bundles of arguments. An argument welcomes any constructive criticism. So if you have one, feel free to state on comment below :)
当然,这些因素只是一堆争论。 一种论点欢迎任何建设性的批评。 因此,如果您有一个,请随时在下面的评论中陈述:)
External links:
外部链接:
https://tenor.com/view/javascript-hadouken-code-programming-gif-16271428
https://tenor.com/view/javascript-hadouken-code-programming-gif-16271428
https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html
https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html
https://en.wikipedia.org/wiki/Single-responsibility_principle
https://www.yegor256.com/2014/05/05/oop-alternative-to-utility-classes.html
https://www.yegor256.com/2014/05/05/oop-alternative-to-utility-classes.html
https://www.yegor256.com/2014/11/20/seven-virtues-of-good-object.html
https://www.yegor256.com/2014/11/20/seven-virtues-of-good-object.html
https://breadcrumbscollector.tech/beware-of-chicken-testing-or-mocks-overuse/
https://breadcrumbscollector.tech/beware-of-chicken-testing-or-mocks-overuse/
https://www.yegor256.com/2014/09/23/built-in-fake-objects.html
https://www.yegor256.com/2014/09/23/built-in-fake-objects.html
https://chercher.tech/kotlin/decorator-design-pattern-kotlin
https://chercher.tech/kotlin/decorator-design-pattern-kotlin
http://www.adapttransformation.com/devops-journey/transformation/tightly-coupled-code/
http://www.adapttransformation.com/devops-journey/transformation/tightly-coupled-code/
https://www.infoworld.com/article/2073649/why-extends-is-evil.html
https://www.infoworld.com/article/2073649/why-extends-is-evil.html
https://en.wikipedia.org/wiki/Liskov_substitution_principle
https://zh.wikipedia.org/wiki/利斯科夫(Liskov_substitution_principle)
https://www.yegor256.com/2014/06/09/objects-should-be-immutable.html
https://www.yegor256.com/2014/06/09/objects-should-be-immutable.html
https://www.yegor256.com/2014/12/09/immutable-object-state-and-behavior.html
https://www.yegor256.com/2014/12/09/immutable-object-state-and-behavior.html
kotlin入门