I guess I won't be mistaken if I say that most often people ask about SOLID principles during interviews. Technologies, languages ββand frameworks are different, but the principles of coding are generally similar: SOLID, KISS, DRY, YAGNI, GRASP and the like are worth knowing for everyone.
In the modern industry, the OOP paradigm has dominated for many decades, and many developers have the impression that it is the best or even worse - the only one. There is a great video on this topic Why Isn't Functional Programming the Norm? about the development of languages ββ/ paradigms and the roots of their popularity.
SOLID was originally described by Robert Martin for OOP and is perceived by many as referring only to OOP, even wikipedia tells us about it, let's look at whether these principles are so tied to OOP?
Single Responsibility
Let's enjoy Uncle Bob 's insight into SOLID :
This principle was described in the work of Tom DeMarco and Meilir Page-Jones. They called it cohesion. They defined cohesion as the functional relatedness of the elements of a module. In this chapter we'll shift that meaning a bit, and relate cohesion to the forces that cause a module, or a class, to change.
Each module should have one reason for changes (and not at all do one thing, as many answer) and as the author himself explained in one of the videos, this means that changes should come from one group / role of people, for example, the module should only change according to business analyst, designer, DBA specialist, accountant or lawyer requests.
Please note that this principle applies to a module, which in OOP is a class. There are modules in almost all languages, so this principle is not limited to OOP.
Open Closed
SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION
Bertrand Meyer
- , β , ( ) .
( , ). , . map
, filter
, reduce
, . , foldLeft
!
def map(xs: Seq[Int], f: Int => Int) =
xs.foldLeft(Seq.empty) { (acc, x) => acc :+ f(x) }
def filter(xs: Seq[Int], f: Int => Boolean) =
xs.foldLeft(Seq.empty) { (acc, x) => if (f(x)) acc :+ x else acc }
def reduce(xs: Seq[Int], init: Int, f: (Int, Int) => Int) =
xs.foldLeft(init) { (acc, x) => f(acc, x) }
, , β .
Liskov Substitution
If for each objecto1
of typeS
there is an objecto2
of typeT
such that for all programsP
defined in terms ofT
, the behavior ofP
is unchanged wheno1
is substituted foro2
thenS
is a subtype ofT
.
, , "" . , , .
"", , "" . , ! , ( ), :
static <T> T increment(T number) {
if (number instanceof Integer) return (T) (Object) (((Integer) number) + 1);
if (number instanceof Double) return (T) (Object) (((Double) number) + 1);
throw new IllegalArgumentException("Unexpected value "+ number);
}
, T
, , "" (.. ), , β .
, , "" , , . , , ( ), , (ad hoc) . .
Interface Segregation
, , , .
, , "" ! , (type classes), .
Comparable
Java
type class Ord
haskell
( class
β haskell
):
//
class Ord a where
compare :: a -> a -> Ordering
"", , , compare
( Comparable
). .
Dependency Inversion
Depend on abstractions, not on concretions.
Dependency Injection, β , :
int first(ArrayList<Integer> xs) // ArrayList ->
int first(Collection<Integer> xs) // Collection ->
<T> T first(Collection<T> xs) //
: ( ):
def sum[F[_]: Monad](xs: Seq[F[Int]]): F[Int] =
if (xs.isEmpty) 0.pure
else for (head <- xs.head; tail <- all(xs.tail)) yield head + tail
sum[Id](Seq(1, 2, 3)) -> 6
sum[Future](Seq(queryService1(), queryService2())) -> Future(6)
, , .
SOLID , . , SOLID , . , !