Software Design/Extract loosely coupled parts of a class into smaller classes
Checklist questions:
- Loosely coupled parts of the state in a class can be extracted into smaller classes?
- There is a cluster of functionality within a class unrelated to the class's primary responsibility?
There is a difference between this refactoring and practice Create classes with a single purpose: a class may have a single purpose on some level of abstraction, yet it may consist of loosely coupled parts. It may be possible to extract these parts as smaller classes on the lower level of abstraction.
This refactoring is a special case of Extract Class.
Examples
editConsider a class like the following:
class Foo {
var field1: SomeType
var field2: Int
var auxFieldA: Long
var auxFieldB: Int
...some functions accessing auxFieldA and auxFieldB directly
// Called from within Foo, or by the clients of Foo
fun auxFunction() {
// accesses auxFieldA and auxFieldB
}
fun anotherAuxFunction() {
// accesses auxFieldA and auxFieldB
}
}
AuxFoo
may be factored out of Foo
:
class AuxFoo {
var fieldA: Long
var fieldB: Int
fun function() {
//...
}
fun anotherFunction() {
//...
}
}
class Foo {
var field1: SomeType
var field2: Int
val aux: AuxFoo = AuxFoo()
...some functions calling to aux.function() or anotherFunction()
// May need to retain this function if it's a part of the Foo's interface
fun auxFunction() {
aux.function()
}
}
See another example of this refactoring on Extract Class catalog entry.
Why
editExtracting parts of class's logic and state into smaller classes reduces the scope of both the extracted fields and the fields that are left in the original class. Then it's harder to make a programming mistake by accessing a wrong field.[1] Extracted classes can protect their state by allowing access only via functions which reduces the probability of erroneous modifications of their state.
The core logic of the original class may become clearer because it's not intermingled with the auxiliary logic.[2] Also, the names of the fields and functions in the extracted class don't have to be prefixed (as in AuxFoo
class in the example above) which also improves the code clarity.
With the core logic and auxiliary parts residing in a single class, it may not be apparent to readers whether the fact that these parts don't interoperate with each other (or interoperate in certain, well-defined ways) is an accidental property or something inherent to the design of the class.
Extracting independent logical parts into smaller classes may help to make the main class more easily testable. In the testing environment, the smaller classes may be replaced with mocks or another implementation (if the extracted class, AuxFoo
in the example above, is turned into an interface) to verify the core logic in the original class.
Separating the core logic and the orthogonal parts of the logic between different classes may allow both the main class and the smaller classes to be reused in the future, even though there may be no need for that at the moment (otherwise practice Don't repeat logic in several places should be applied anyway).
Why not
editRelated
editReferences
edit- ↑ Boswell, Dustin; Foucher, Trevor (2011). The Art of Readable Code. ISBN 978-0596802295. Chapter 9, "Shrink the Scope of Your Variables" section
- ↑ Domain Probe example in an article about Domain-Oriented Observability
Sources
edit- Refactoring: Improving the Design of Existing Code (2 ed.). 2018. ISBN 978-0134757599. https://martinfowler.com/books/refactoring.html. Chapter 3, "Data Clumps" and "Large Class" sections