This lesson was translated automatically – it may contain errors.
The problem of modifying a system matters most when the requirements keep changing.
Often the points of instability – the places that will change more often than the rest – can be spotted in advance. Those points are then defined as interfaces, and a change hides behind the boundary instead of spreading across the whole system.
// A point of instability: the way notifications are delivered will change
public interface INotifier
{
void Notify(User user, string message);
}
public class EmailNotifier : INotifier { public void Notify(User u, string m) { } }
public class SmsNotifier : INotifier { public void Notify(User u, string m) { } }
// A point of instability: the way notifications are delivered will change
class INotifier {
public:
virtual void Notify(const User& user, const std::string& message) = 0;
virtual ~INotifier() = default;
};
class EmailNotifier : public INotifier {
public:
void Notify(const User& user, const std::string& message) override {}
};
class SmsNotifier : public INotifier {
public:
void Notify(const User& user, const std::string& message) override {}
};
// A point of instability: the way notifications are delivered will change
type Notifier interface {
Notify(user User, message string) error
}
type EmailNotifier struct{}
func (EmailNotifier) Notify(user User, message string) error { return nil }
type SMSNotifier struct{}
func (SMSNotifier) Notify(user User, message string) error { return nil }
# A point of instability: the way notifications are delivered will change
from typing import Protocol
class Notifier(Protocol):
def notify(self, user: User, message: str) -> None: ...
class EmailNotifier:
def notify(self, user: User, message: str) -> None:
pass
class SmsNotifier:
def notify(self, user: User, message: str) -> None:
pass
// A point of instability: the way notifications are delivered will change
interface Notifier {
notify(user: User, message: string): void;
}
class EmailNotifier implements Notifier {
notify(user: User, message: string): void {}
}
class SmsNotifier implements Notifier {
notify(user: User, message: string): void {}
}
// A point of instability: the way notifications are delivered will change
public interface Notifier {
void notify(User user, String message);
}
public class EmailNotifier implements Notifier {
public void notify(User user, String message) { }
}
public class SmsNotifier implements Notifier {
public void notify(User user, String message) { }
}
// A point of instability: the way notifications are delivered will change
fun interface Notifier {
fun notify(user: User, message: String)
}
class EmailNotifier : Notifier {
override fun notify(user: User, message: String) = Unit
}
class SmsNotifier : Notifier {
override fun notify(user: User, message: String) = Unit
}
The main difficulty
The principle requires guessing what exactly is going to change. Protection against changes that never happened is extra interfaces and extra layers, paid for by everyone who reads the code.
A practical rule: protect what has already changed at least once, not what could theoretically change.