In my application I have N child classes all extending a superclass. Every class has its own implementation of serialize but they share some common code. For example:
class Serializer {
serialize = () => throw Exception("...")
}
class JSONSerializer extends Serializer {
serialize = () => {
console.log("Formatting...")
// ...
}
}
class XMLSerializer extends Serializer {
serialize = () => {
console.log("Formatting...")
// ...
}
}
Do I violate the SOLID principles (in particular interface inheritance over implementation inheritance -Liskov substitution-) if I wrap the common code inside the superclass in a function format and call it inside the child classes? If yes, how can I adhere to the principle?
class Serializer {
format = () => console.log("Formatting...")
serialize = () => throw Exception("...")
}
class JSONSerializer extends Serializer {
serialize = () => {
this.format()
// ...
}
}
class XMLSerializer extends Serializer {
serialize = () => {
this.format()
// ...
}
}
Your modifications to the code do not violate the SOLID principles. Extracting common code into the superclass and calling them inside the child classes aligns with the principles.
Here's why:
In addition, Dry (Don't Repeat Yourself) principle is well applied by moving the common code format into the parent class Serializer, thereby enhancing code maintainability and reducing potential errors.
Just a note of caution though, throwing exceptions directly from super-class might not be an ideal practice, instead, you could structure it to return a not implemented message or handling it in some other way.
Interface inheritance (also known as subtyping) is when a class provides the implementation for the methods of an interface. This type of inheritance is purely a "contract" that ensures the class adheres to a specific structure. In this case, a class agrees to implement a series of methods with specific input and return types.
On the other hand, implementation inheritance (or subclassing) is when a class inherits behaviors (methods) and state (properties) from another class, which we call a superclass or a parent class. The idea behind this is to create more specific classes based on a general class.
Your example is more about implementation inheritance since you're making subclasses
JSONSerializerandXMLSerializerthat bring over the format method from the parentSerializerclass.Now, in terms of SOLID principles, it's generally recommended to prefer composition over inheritance and to rely on interfaces (interface inheritance). This principle is known as the Dependency Inversion Principle, the 'D' in SOLID. This principle tells us that the high-level modules should not depend on low-level modules directly but should depend on abstractions (usually interfaces). But, each case has its own context and needs, so there could be situations where implementation inheritance is more applicable.
To apply
compositionin your case, you could:1 - Extracting the shared
format()method into a separate Formatter class:2- Instead of extending Serializer, our
JSONSerializerandXMLSerializerclasses will include an instance of Formatter to handle formatting:Or if you prefer with dependecy injection: