확실하게 이해를 해서 대충 얼버무리지 않으려 기록을 해야겠다! 또 프로젝트하는데 내가 과연 객체 지향적으로 프로그래밍을 하고 있는지 한번 더 개념을 숙지할 필요성이 생겼다!!!
정의 : 하나의 클래스는 하나의 책임만 가져야 한다.
단일 책임 원칙 "Bad" 예제에서 UserSettings 클래스는 설정을 변경하고 사용자 인증을 모두 처리하고 있습니다. 이는 두 가지 책임을 갖게 되므로 SRP를 위반합니다. "Good" 예제에서는 UserAuth와 UserSettings 두 클래스로 책임이 분리되어 각각 하나의 책임만 가지게 됩니다. 이렇게 하면 코드의 복잡성이 줄어들고, 유지보수가 용이해집니다.
// Bad
class UserSettings {
constructor(user) {
this.user = user;
}
changeSettings(settings) {
if (this.verifyCredentials()) {
// ...
}
}
verifyCredentials() {
// ...
}
}
// Good
class UserAuth {
constructor(user) {
this.user = user;
}
verifyCredentials() {
// ...
}
}
class UserSettings {
constructor(user) {
this.user = user;
this.auth = new UserAuth(user);
}
changeSettings(settings) {
if (this.auth.verifyCredentials()) {
// ...
}
}
}
원인 : 소포트웨어 요소는 확장에는 열려있고, 변경에는 닫혀 있어야 한다.
개방-폐쇄 원칙 "Bad" 예제에서 만약 다른 형태의 도형(예: 원)의 넓이를 계산하려면 기존 코드를 수정해야 합니다. "Good" 예제에서는 Shape라는 상위 클래스를 만들고, Rectangle은 Shape을 상속받아 area 메서드를 구현합니다. 이후에 다른 도형(예: 원)의 넓이를 계산하려면 Shape을 상속받아 구현하기만 하면 됩니다.
// Bad
class Rectangle{
constructor(width, height){
this.width = width;
this.height = height;
}
area(){
return this.width * this.height;
}
}
let rectangles= [new Rectangle(2,3), new Rectangle(3,6)];
let totalArea=0;
rectangles.forEach((rectangle)=>{
totalArea += rectangle.area();
});
console.log(totalArea);
// Good
class Shape{
area(){
throw new Error('Area method should be implemented');
}
}
class Rectangle extends Shape{
constructor(width, height){
super();
this.width = width;
this.height = height;
}
area(){
return this.width *this.height;
}
}
let shapes= [new Rectangle(2,3), new Rectangle(3,6)];
let totalArea=0;
shapes.forEach((shape)=>{
totalArea += shape.area();
});
console.log(totalArea);
정의 : 상위 타입은 항상 하위 타입으로 대체할 수 있어야 한다.
"Bad" 예제에서 Square 클래스가 Rectangle 클래스를 상속받았지만, setWidth와 setHeight 메서드가 부모클래스와 다르게 동작합니다.
// Bad: Square가 Rectangle을 상속받지만, setWidth와 setHeight 메서드에서 같은 동작을 하지 않습니다.
class Rectangle{
setWidth(width){this.width=width;}
setHeight(height){this.height=height;}
getArea(){return width*height;}
}
class Square extends Rectangle{
setWidth(width){super.setWidth(width);super.setHeight(width);}
setHeight(height){super.setWidth(height);super.setHeight(height);}
}
function increaseRectangleWidth(rectangle){
rectangle.setWidth(rectangle.getWidth()+1);
return rectangle.getArea();
}
const rectangle=new Rectangle();
rectangle.setWidth(4);
rectangle.setHeight(5);
const square=new Square();
square.setWidth(5);
console.log(increaseRectangleWidth(rectangle));
console.log(increaseRectangleWidth(square));
// Good: 각각의 클래스가 자신의 행동을 정의합니다.
class Shape{
getArea(){throw new Error("Area method should be implemented");}
}
class Rectangle extends Shape{
setWidth(width){this.width=width;}
setHeight(height){this.height=height;}
getArea(){return this.width*this.height;}
}
class Square extends Shape{
setLength(length){this.length=length;}
getArea(){return this.length*this.length;}
}
function increaseRectangleWidth(rectangle){
rectangle.setWidth(rectangle.getWidth()+1);
return rectangle.getArea();
}
const rectangle=new Rectangle();
rectangle.setWidth(4);
rectangle.setHeight(5);
const square=new Square();
square.setLength(5);
console.log(increaseRectangleWidth(rectangle));
console.log(square.getArea());
정의 : 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
공통 인터페이스 1개보다는 특정 목적을 위한 인터페이스 여러 개가 있는 것이 좋다.
"Bad" 예제에서 Robot은 eat 메소드에 대해서도 구현해야 하는데 실질적으로 로봇은 음식을 먹지 않습니다. "Good" 예제에서 Worker와 Robot은 필요한 인터페이스만 구현하도록 분리되었습니다.
// Bad
interface IWorker {
work(): void;
eat(): void;
}
class Worker implements IWorker {
work() { /* ... */ }
eat() { /* ... */ }
}
class Robot implements IWorker {
work() { /* ... */ }
eat() { // Robots cannot eat, but they have to implement the method.
throw new Error('Cannot eat');
}
}
// Good
interface IWorker {
work(): void;
}
interface IEater {
eat(): void;
}
class Worker implements IWorker, IEater {
work() { /* ... */ }
eat() { /* ... */ }
}
class Robot implements IWorker {
work() { /* ... */}
}
정의 : 개발자는 추상화에 의존해야지, 구체화에 의존하면 안 된다.
즉, 구현 클래스가 아닌 인터페이스에 의존하라는 의미
의존 역전 원칙 "Bad" 예제에서 ElectricPowerSwitch 클래스가 LightBulb과 직접적으로 연결되어 있습니다. "Good" 에서 ElectricPowerSwitch는 SwitchableDevice라는 추상화에 의존하여, 여러 종류의 장치에 대해 동작할 수 있게 되었습니다.
// Bad
class LightBulb {}
class ElectricPowerSwitch {
constructor(lightBulb) {
this.lightBulb = lightBulb;
this.on = false;
}
press(){
if (this.on) console.log('The lightbulb is already on.');
else console.log('The lightbulb is now on.');
this.on = !this.on;
}
}
// Good
class SwitchableDevice {}
class LightBulb extends SwitchableDevice {}
class Fan extends SwitchableDevice {}
// Now ElectricPowerSwitch is not dependent on LightBulbs anymore, but on the abstraction.
class ElectricPowerSwitch {
constructor(device) {
this.device = device;
this.on = false;
}
press(){
if (this.on) console.log('The device is already on.');
else console.log('The device is now on.');
this.on = !this.on;
}
}
숙지또 숙지하자!!