싱글톤에서 파사드까지

10339 단어 programmingphpwebdev
어떤 사람들은 싱글톤이 악하다는 말을 들었습니다. 그들은. 사용하지 마십시오.

TL;DR And don't use facades either, if you don't have to.



더 나은 내일을 향해



"하지만 싱글톤은 사용하기가 너무 쉽습니다!"👴

// Singleton
class SomeService
{
    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new static();
        }
        return self::$instance;
    }
}


또한 싱글톤의 유용성을 유지하면서 여전히 종속성 주입을 어느 정도 사용하는 것도 간단합니다.
완벽하지는 않지만 불완전한 세상을 위한 좋은 절충안입니다.

싱글톤과 파사드 사이에는 단 하나의 차이점이 있으며 인스턴스의 명시적 바인딩입니다.
파사드를 사용하기 전에 명시적 바인딩 호출이 발생해야 합니다.

// Facade
class SomeService
{
    public static function getInstance(): self {
        if (self::$instance === null) {
            throw new LogicException();
        }
        return self::$instance;
    }

    public static function bind(?self $instance): void
    {
        self::$instance = $instance;
    }
}


바인딩은 일반적으로 앱의 서비스 컨테이너를 사용하여 애플리케이션의 부트스트랩 단계에서 수행됩니다.

// Bootstrap the service container somehow...
$container = $bootstrap->container();

// Then bind the facade
SomeService::bind($container->get(SomeService::class));


이제 서비스 컨테이너를 통한 주입과 파사드 직접 사용이 모두 작동합니다.

이렇게 하면 레거시 코드가 계속 작동하도록 레거시 앱을 리팩터링할 수 있지만 최신 부분에 종속성 주입이 도입됩니다.

인터페이스 사용 예



파사드를 구현할 때 바인딩되는 인스턴스에 대해 항상 인터페이스를 사용해야 합니다. 이렇게 하면 그 뒤에 있는 구현을 교체할 수 있습니다.

final class EventBus
{
    private static ?EventDispatcherInterface $instance = null;

    public static function fire(EventInterface $event): void
    {
        if (self::$instance === null) {
            throw new LogicException('The facade has not been bound to an instance.');
        }
        self::$instance->dispatch($event);
    }

    public static function bind(?EventDispatcherInterface $instance, bool $force = false): void
    {
        if (!$force && self::$instance !== null) {
            throw new LogicException(...);
        }
        self::$instance = $instance;
    }
}


앱 시작 중에 구성

$container = ...;
EventBus::bind($container->get(EventDispatcherInterface::class));


이제 DI와 파사드가 공존할 수 있습니다.

$event = new SomeEvent(...);

$dispatcher = $container->get(EventDispatcherInterface::class);
$dispatcher->dispatch($event);

EventBus::fire($event);

$force 메서드의 EventBus::bind 매개변수를 사용하는 이유를 물을 수 있습니다. 실용적인 목적을 위해: 파사드를 테스트하고 이를 위해 리바인딩하고 싶을 것입니다.

또한 다음과 같이 이미 바인딩된 파사드를 바인딩하려고 시도할 때 설명적 예외를 throw하는 것이 좋습니다.

throw new LogicException(sprintf(
    'The facade has already been bound.' . ' ' .
    'This error might imply design flaws.' . ' ' .
    'Consider injecting an %s implementation instead of using a facade.',
    EventDispatcherInterface::class,
));


싱글톤은 모든 악의 근원입니까?



잘못된 생각을 하지 마십시오. 서비스에 대한 전역 액세스를 허용하기 위해 파사드를 사용해서는 안 됩니다. 여전히 종속성 주입 및 제어 역전(IoC) 원칙을 사용하려고 합니다.

Facades는 싱글톤을 구동하는 전역 액세스에 대한 동일한 나쁜 아이디어를 기반으로 합니다.

반면에 실용적인 경우도 있습니다(예: 로깅).

그리고 이미 싱글톤과 함께 제공되는 레거시 앱이 있습니다.
또는 종속성 주입 수단을 전혀 사용하지 않는 암흑기의 레거시 앱입니다.

파사드를 구현하고 DI 컨테이너를 활용하는 것은 이러한 상황에서 큰 진전이 될 수 있습니다.

그렇다면 싱글톤에 어떤 문제가 있습니까?



타이트한 커플링. 음, 모든 정적 호출 및 new 연산자 사용은 긴밀한 결합을 생성합니다.

밀접하게 결합된 코드는 더 이상 사용되지 않는 구현을 새 구현으로 교체할 수 없기 때문에 확장하기 어렵고 유지하기가 더 어렵습니다. 밀접하게 결합된 코드는 코드의 많은 부분을 모킹해야 하기 때문에 테스트하기가 더 어렵습니다.

인터페이스를 통해 인스턴스에 바인딩되는 파사드로 작업할 때 파사드 뒤의 구현은 호출 코드를 수정하지 않고도 변경할 수 있습니다.
이렇게 하면 문제가 다소 완화됩니다.

싱글톤이 존재하는 이유는 무엇입니까?



싱글톤 패턴은 암흑기의 더 큰 악인 전역 변수를 해결했습니다. 싱글톤은 적어도 불변인 반면 전역 변수는 변덕스럽게 바뀔 수 있습니다.
싱글톤은 프로그래밍 역사의 일부이며 존중받아 마땅합니다. 그러나 현대 코드에는 그것들을 위한 자리가 없습니다.

테이크아웃



외관은 우리를 동시에 행복하고 편안하게 만드는 깨끗한 솔루션이 아닙니다. 그러나 싱글톤보다 더 나은 옵션입니다.
특히 기존 코드와의 호환성을 제공하는 동시에 더 밝은 미래를 향한 길을 닦는 레거시 앱에서 그렇습니다.

좋은 웹페이지 즐겨찾기