在Kotlin无DI场景下,如何编写基类实现DRY原则?
Hey there! Let's fix that duplicate code you're dealing with. Your two User classes are nearly identical—only differing in how they initialize the name property. Here are two clean approaches to extract the common logic into an abstract base class:
Approach 1: Base Class Accepts a Pre-Initialized Name Instance
This is my go-to for cases where the initialization logic is simple and you might want to merge the two User classes into one with multiple constructors:
// Abstract base class holding shared logic abstract class BaseUser(protected val name: Name) { // Shared method - no more repetition! fun sayName() { println(name.fakeName) } } // Single User class with two constructors covering both cases class User : BaseUser(NameGenerator()) { // Secondary constructor for the suggestion-based initialization constructor(suggestion: String) : this(NameGenerator(suggestion)) }
How this works:
- The
BaseUsertakes a fully initializedNameinstance in its constructor, so it doesn't need to worry about howNameis created—only that it exists. - The
Userclass uses a primary constructor for the no-argument case, and a secondary constructor to handle the suggestion parameter, passing the appropriateNameGeneratorresult up to the base class. - You can now use both
User()andUser("my-suggestion")just like your original two classes, but with zero duplicated code.
Approach 2: Abstract name Property for Subclass-Specific Initialization
If you prefer to keep the two separate classes (maybe they'll diverge more later), this approach lets each subclass handle its own name initialization while sharing the sayName() method:
abstract class BaseUser { // Subclasses must provide their own implementation of this property protected abstract val name: Name // Shared method remains in the base class fun sayName() { println(name.fakeName) } } // No-argument User class class User : BaseUser() { override val name: Name = NameGenerator() } // Suggestion-based User class (rename if you want to keep it distinct) class SuggestionUser(suggestion: String) : BaseUser() { override val name: Name = NameGenerator(suggestion) }
How this works:
BaseUserdeclares an abstractnameproperty, forcing each subclass to define how it's initialized.- Both subclasses override
namewith their specificNameGeneratorcall, while inheriting thesayName()method for free.
Either approach will eliminate your duplicate code and keep things clean. If the two initialization styles are just variations of the same user type, Approach 1 is more concise. If they're likely to grow into distinct types later, Approach 2 gives you more flexibility.
内容的提问来源于stack exchange,提问作者Albert Gao

