I'm thinking of putting together anti-patterns that I often see while working. This time it is a violation of Demeter's law. The code is Scala, but it's basically the same for Ruby, Python, and Java.
The topics are below.
Demeter's law is that you should not use anything other than "directly instantiated" or "passed as an argument".
class Profile(name: String, age: Int) {
def showProfile(): Unit = println(s"my name is $name (age: $age)")
}
class Applicant(id: String, val profile: Profile)
object Order {
//Violates Demeter's Law
def showApplicant(applicant: Applicant): Unit = {
applicant.profile.showProfile()
}
}
ʻApplicantis passed as an argument, but in the method, It violates because the
showProfile ()method is executed after getting the
profile in ʻapplicant.profile
.
ʻApplicant.profile.showProfile ()because I want to do it I think you should hesitate to ** publish properties with getters ** with
val like
class Applicant (id: String, val profile: Profile) `.
The form you want to call is as follows.
def showApplicant(applicant: Applicant): Unit = {
applicant.showProfile()
}
Here, the following is described.
class Profile(name: String, age: Int) {
def showProfile(): Unit = println(s"my name is $name (age: $age)")
}
//Applicant inherits Profile
class Applicant(id: String, name: String, age: Int) extends Profile(name, age)
object Order {
def showApplicant(applicant: Applicant): Unit = {
applicant.showProfile()
}
}
↑ It feels strange, so fix it with the transfer method.
class Profile(name: String, age: Int) {
def showProfile(): Unit = println(s"my name is $name (age: $age)")
}
//Since the profile no longer needs to be published by getters
//I removed the val.
class Applicant(id: String, profile: Profile) {
//Transfer method
def showProfile(): Unit = profile.showProfile()
}
object Order {
def showApplicant(applicant: Applicant): Unit = {
applicant.showProfile()
}
}
Now consider testing the ʻOrder.showApplicant ()` method modified by the ** transfer method **.
object SimpleTest extends App {
//Create the required objects
val profile = new Profile("taro", 22)
val applicant = new Applicant("001", profile)
//Execute the method under test
Order.showApplicant(applicant)
}
In the above example, it is easy to create an instance of ʻApplicant, but for example, consider the case where ʻApplicant
aggregates various classes and instantiation is awkward.
The important thing is that ** the test target is the ʻOrder.showApplicant ()` method, which is a transfer method, so it's good if you can confirm that you called it. That means **.
object ComplexTest extends App {
//Complex instantiation
val profile1 = ...
val profile2 = ...
val profile3 = ...
val applicant = new Applicant("001", profile1, profile2, profile3, ...)
//I just want to execute the method under test,
//Instance generation is a pain.
Order.showApplicant(applicant)
}
As long as the ʻOrder.showApplicant () method can be executed, the content of the argument ʻapplicant
can be anything.
So, sandwich the interface so that ʻOrder.showApplicant ()` depends on abstraction.
//Keep abstract
trait IApplicant {
def showProfile(): Unit
}
class Applicant(id: String, profile: Profile) extends IApplicant {
def showProfile(): Unit = profile.showProfile()
}
object Order {
//The applicant type is now IApplicant and depends on abstraction.
def showApplicant(applicant: IApplicant): Unit = {
applicant.showProfile()
}
}
The ʻapplicant required for the argument of the ʻOrder.showApplicant ()
method can be generated as follows, which makes the test a little easier.
object RefactorComplexTest extends App {
//Complex instantiation
val applicant = new IApplicant {
override def showProfile(): Unit = println("Come on. suitable. Come on")
}
Order.showApplicant(applicant)
}
If you continue to violate Demeter's law as shown below, it will be difficult to test, so I personally think that if you violate Demeter's law, you should fix it.
You can test it by using a mock, but I personally think that it is better to review the design before using the mock.
def showApplicant(applicant: Applicant): Unit = {
//It is difficult to test if you continue to violate Demeter's law
applicant.profile.showProfile()
}
Recommended Posts