Java를 사용하여 SOLID 설계 원칙 배우기

19031 단어 javaprogramming
이 게시물here도 볼 수 있습니다.

이 기사의 주요 아이디어는 SOLID 설계 원칙을 보여주고 Java를 기본 언어로 사용하여 이러한 원칙을 구현하는 예를 제공하는 것입니다.

솔리드는 무엇입니까?



S.O.L.I.D는 다음을 의미합니다.

Single Responsibility Principle

Open Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

디자인 원칙은 일반적으로 더 나은 소프트웨어를 작성하는 데 도움이 됩니다. 또한 동일한 코드베이스를 공유하는 개발자의 개발자 경험도 향상됩니다.

단일 책임 원칙



클래스는 하나의 책임만 가져야 합니다.

새로운 구성원을 코드에 온보딩하고 코드베이스를 테스트, 유지 관리 및 성장시키는 데 도움이 됩니다.

예시



다음과 같은 UserService가 있다고 상상해 보십시오.

class UserService {

    public void createUser() {
        // Create our user here
    }

    public User findById(UUID id) {
        // Return our user
    }

    private void sendAppNotification() {
        // Send an app notification
    }

    private void sendEmail() {
        // Send an email
    }
}


이 클래스의 요구 사항이 변경되면 어떻게 됩니까? 이제 푸시나 SMS를 통해 앱 알림을 보내야 하는 지금 텍스를 보내야 한다면 어떻게 될까요?

그런 다음 모든 커뮤니케이션을 처리할 NotificationService를 생성하여 관심사와 책임을 분리해야 합니다.

class UserService {

    private final NotificationService notificationService;

    public void createUser() {
        // Create our user here
    }

    public User findById(UUID id) {
        // Return our user
    }
}



class NotificationService {
    public void sendSms() {
        // Send SMS
    }

    public void sendPushNotification() {
        // Send push
    }

    public void sendEmail() {
        // Send email
    }
}


여기에서 더 나은 접근 방식을 가질 수 있지만 아이디어는 관심사를 분리하는 것이며 이러한 종류의 문제를 해결하기 위한 다른 솔루션은 다른 주제입니다.

개방 폐쇄 원칙



소프트웨어 엔터티(클래스, 모듈, 함수 등)는 확장에는 열려 있지만 수정에는 닫혀 있어야 합니다.

예시



CoffeeApp 클래스가 있다고 생각하십시오.

public class CoffeeApp {
    public void brewSimpleCoffee() {
        // Put water
        // Put coffee powder
    }

    public void brewPremiumCoffee() {
        // Get the bean
        // Grind the bean
        // Put water
    }
}


시스템이 새로운 유형의 커피를 지원해야 할 때마다 새로운 유형을 추가하기 위해 머신을 변경해야 한다고 상상해 보십시오.

이것을 개선하기 위해 간단한 추상화와 다형성을 사용할 수 있습니다.

public class CoffeeApp {
    public static void greet(CoffeeMachine coffeeMachine) {
        coffeeMachine.brewCoffee(ESPRESSO);
    }
}

interface CoffeeMachine {
    Coffee brewCoffee(CoffeeSelection selection);
}

public class BasicCoffeeMachine implements CoffeeMachine {
    private BrewingUnit brewingUnit;

    @Override
    public Coffee brewCoffee(CoffeeSelection selection) {
        brewFilterCoffee();
    }

    private brewFilterCoffee() {
        brewingUnit.brew();
    }
}

public class PremiumCoffeeMachine implements CoffeeMachine {
    private Grinder grinder;
    private BrewingUnit brewingUnit;

    @Override
    public Coffee brewCoffee(CoffeeSelection selection) {
    switch(selection) {
        case ESPRESSO:
            return brewEspresso();
        case FILTER_COFFEE:
        default:
            return brewFilterCoffee();
        }
    }

    private brewEspresso() {
        grinder.grind()
        brewingUnit.brew();
    }

    private brewFilterCoffee() {
        brewingUnit.brew();
    }
}


이제 해당 클래스는 확장(ItalianMachine, ColombianMachine, FrenchPressMachine)을 위해 열리고 수정을 위해 닫힙니다(새 머신이 앱에 추가될 때마다 다른 논리가 추가되는 메서드가 없음).

Liskov 대체 원리



클래스는 모든 실제 사용 시나리오에서 하위 클래스로 대체될 수 있습니다. 즉, 대체 가능성을 위해서만 상속을 사용해야 합니다.

예시



동물 예제 사용

interface Animal {
    void fly();
    void swim();
}

public class Dog implements Animal {
    // A dog can swim
    @Override
    public void swim() {
        // Swim
    }

    // But a dog cannot fly
    @Override
    public void fly() {
        throw IllegalStateException();
    }
}

public class Hawk implements Animal {
    // A hawk cannot swim
    @Override
    public void swim() {
        throw IllegalStateException();
    }

    // But a hawk can fly
    @Override
    public void fly() {
       // Fly
    }
}


aDog와 aHawk 모두 동물이지만 이 원칙을 따르기 위해 상속을 분리할 수 있습니다.

interface Animal {
    void swim();
}

interface Bird {
    void fly();
}

public class Dog implements Animal {
    // A dog can swim
    @Override
    public void swim() {
        // Swim
    }
}

public class Hawk implements Bird {
    // A hawk can fly
    @Override
    public void fly() {
       // Fly
    }
}


인터페이스 분리 원리



클라이언트는 사용하지 않는 인터페이스를 강제로 구현해서는 안 됩니다.

예시



마지막 예를 생각해 보면 Dog는 수영만 할 수 있지만 일부Animal는 수영하고 날 수 있습니다.

이와 같은 인터페이스를 구현하면 쉽지 않습니다.

interface Swimmer {
    void swim();
}

interface Bird {
    void fly();
}

public class Dog implements Swimmer {
    // A dog can swim
    @Override
    public void swim() {
        // Swim
    }
}

public class Hawk implements Bird {
    // A hawk can fly
    @Override
    public void fly() {
       // Fly
    }
}

public class Duck implements Swimmer, Bird {
    // A duck can fly
    @Override
    public void fly() {
       // Fly
    }

    // A duck can swim
    @Override
    public void swim() {
        // Swim
    }
}


종속성 역전 원칙



상호 작용을 추상화하여 상위 수준 모듈과 하위 수준 모듈 간의 고전적인 종속성을 반전시켜야 합니다.

예시



데이터베이스에 대한 구현이 있다고 가정해 보겠습니다.

public class PersonService {
    private final PersonRepository personRepository;

    public PersonService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }
}

interface PersonRepository {
    Person findById(UUID id);
    Person create(UUID id, String name);
}

public class LocalRepository implements PersonRepository {
    private Map<UUID, Person> repository;

    // Implement the methods
}

public class DatabaseRepository implements PersonRepository {
    // Hibernate entity manager to handle the communication towards the Database.
    private EntityManager entityManager;

    // Implement the methods
}


이러한 방식으로 상위 수준의 PersonService는 개발을 위해 LocalDatabase를 사용하는지 프로덕션을 위해 실제 데이터베이스를 사용하는지 상관하지 않습니다.

예를 들어, 무슨 일이 일어나고 있는지 알지 못하는 고급 서비스 없이도 Hibernate 구현을 다른 솔루션으로 변경할 수 있습니다.


질문이나 제안 사항이 있는 경우 언제든지 저에게 message를 보내주십시오.

좋은 웹페이지 즐겨찾기