[design-patterns] PHP 프로젝트에서 도우미 개체를 저장, 액세스 및 구성하는 데 어떤 패턴이 있습니까? [닫은]

PHP 기반의 객체 지향 프로젝트에서 데이터베이스 엔진, 사용자 알림, 오류 처리 등과 같은 도우미 객체를 어떻게 구성하고 관리합니까?

큰 PHP CMS가 있다고 가정합니다. CMS는 다양한 수업으로 구성됩니다. 몇 가지 예 :

  • 데이터베이스 객체
  • 사용자 관리
  • 항목 생성 / 수정 / 삭제를위한 API
  • 최종 사용자에게 메시지를 표시하는 메시징 개체
  • 올바른 페이지로 이동하는 컨텍스트 핸들러
  • 버튼을 표시하는 탐색 모음 클래스
  • 로깅 객체
  • 가능하면 사용자 정의 오류 처리

기타

나는 영원한 질문을 다루고 있습니다. 어떻게 이러한 객체를 필요로하는 시스템의 각 부분에 접근 할 수있게 만드는지에 대한 것입니다.

몇 년 전의 첫 번째 접근은 이러한 클래스의 초기화 된 인스턴스를 포함하는 $ application 전역을 갖는 것이 었습니다.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

그런 다음 Singleton 패턴과 공장 기능으로 변경했습니다.

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

하지만 그것도 만족스럽지 않습니다. 단위 테스트와 캡슐화는 나에게 점점 더 중요 해지고 있으며, 글로벌 / 싱글 톤의 논리를 이해하면 OOP의 기본 아이디어가 파괴됩니다.

물론 각 개체에 필요한 도우미 개체에 대한 여러 포인터를 제공 할 가능성이 있습니다. 아마도 가장 깨끗하고 리소스를 절약하고 테스트하기 쉬운 방법이지만 장기적으로는 이것의 유지 관리 가능성에 의문이 있습니다.

내가 살펴본 대부분의 PHP 프레임 워크는 단일 패턴 또는 초기화 된 객체에 액세스하는 함수를 사용합니다. 둘 다 좋은 접근 방식이지만 내가 말했듯이 나는 둘 다 만족하지 않습니다.

여기에 어떤 공통 패턴이 존재하는지에 대한 내 시야를 넓히고 싶습니다. 장기 적이고 실제적인 관점 에서이를 논의하는 리소스에 대한 예제, 추가 아이디어 및 포인터를 찾고 있습니다 .

또한 이 문제에 대한 전문적, 틈새 시장 또는 평범한 이상한 접근 방식에 대해 듣고 싶습니다 .



답변

나는 Flavius가 제안한 Singleton 접근 방식을 피할 것입니다. 이 접근 방식을 피해야하는 이유는 많습니다. 그것은 좋은 OOP 원칙을 위반합니다. Google 테스트 블로그에는 Singleton에 대한 좋은 기사와이를 방지하는 방법이 있습니다.

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html
http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html
http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

대안

  1. 서비스 제공자

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. 의존성 주입

    http://en.wikipedia.org/wiki/Dependency_injection

    그리고 PHP 설명 :

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

다음은 이러한 대안에 대한 좋은 기사입니다.

http://martinfowler.com/articles/injection.html

DI (종속성 주입) 구현 :

Flavius의 솔루션에 대한 더 많은 생각. 나는이 포스트가 안티 포스트가되기를 원하지 않지만, 적어도 나에게는 왜 의존성 주입이 글로벌보다 나은지 아는 것이 중요하다고 생각합니다.

‘진정한’ Singleton 구현 은 아니지만 Flavius가 잘못했다고 생각합니다. 전역 상태가 잘못되었습니다 . 이러한 솔루션은 또한 테스트하기 어려운 정적 메서드를 사용 합니다 .

나는 많은 사람들이 그것을하고 그것을 승인하고 사용한다는 것을 알고 있습니다. 그러나 Misko Heverys 블로그 기사 ( 구글 테스트 가능성 전문가 )를 읽고 다시 읽고 그가 말한 내용을 천천히 소화하면 디자인을 보는 방식이 많이 달라졌 습니다.

애플리케이션을 테스트 할 수 있으려면 애플리케이션 디자인에 다른 접근 방식을 채택해야합니다. 테스트 우선 프로그래밍을 할 때 다음과 같은 일에 어려움을 겪을 것입니다. ‘다음에이 코드에서 로깅을 구현하고 싶습니다. 기본 메시지를 기록하는 테스트를 먼저 작성해 보겠습니다. ‘그런 다음 대체 할 수없는 글로벌 로거를 작성하고 사용하도록 강요하는 테스트를 생각해보십시오.

나는 여전히 그 블로그에서 얻은 모든 정보로 어려움겪고 있으며 구현하기가 항상 쉬운 것은 아니며 많은 질문이 있습니다. 그러나 Misko Hevery가 말한 것을 파악한 후에는 이전에했던 작업 (예, 글로벌 상태 및 Singletons (big S))으로 돌아갈 수있는 방법이 없습니다. 🙂


답변

class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

이것이 내가 할 방법입니다. 요청시 객체를 생성합니다.

Application::foo()->bar();

내가하는 방식이고, OOP 원칙을 존중하고, 지금하는 방식보다 코드가 적으며, 코드가 처음으로 필요할 때만 객체가 생성됩니다.

참고 : 내가 제시 한 것은 실제 싱글 톤 패턴도 아닙니다. 싱글 톤은 생성자 (Foo :: __ constructor ())를 private으로 정의하여 자신의 인스턴스를 하나만 허용합니다. 모든 “응용 프로그램”인스턴스에서 사용할 수있는 “전역”변수 일뿐입니다. 그것이 좋은 OOP 원칙을 무시하지 않기 때문에 그 사용이 유효하다고 생각하는 이유입니다. 물론,이 “패턴”도 세상의 어떤 것으로도 남용되어서는 안됩니다!

저는 이것이 많은 PHP 프레임 워크, Zend Framework 및 Yii에서 사용되는 것을 보았습니다. 그리고 프레임 워크를 사용해야합니다. 나는 당신에게 어떤 것을 말하지 않을 것입니다.

부록 TDD에
대해 걱정하는 사람들을 위해 여전히 일부 배선을 구성하여 종속성 주입을 수행 할 수 있습니다. 다음과 같이 보일 수 있습니다.

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

개선의 여지가 충분합니다. 그것은 단지 PoC입니다. 상상력을 발휘하십시오.

왜 그런가요? 대부분의 경우 애플리케이션은 단위 테스트를 거치지 않고 실제로 프로덕션 환경에서 실행 됩니다 . PHP의 강점은 속도입니다. PHP는 Java와 같은 “깨끗한 OOP 언어”가 아닙니다.

애플리케이션 내에는 하나의 Application 클래스와 각 헬퍼의 인스턴스가 하나만 있습니다 (위와 같이 지연로드에 따라). 물론, 싱글 톤은 나쁘지만 실제 세계를 따르지 않는 경우에만 그렇습니다 . 제 예에서는 그렇습니다.

“싱글 톤은 나쁘다”와 같은 고정 관념 “규칙”은 악의 근원이며, 스스로 생각하지 않으려는 게으른 사람들을위한 것입니다.

예, 알고 있습니다. PHP 선언문은 기술적으로 말하면 나쁩니다. 그러나 그것은 끔찍한 방식으로 성공적인 언어입니다.

추가

하나의 기능 스타일 :

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();


답변

나는 의존성 주입의 개념을 좋아한다.

“종속성 주입은 구성 요소가 생성자, 메서드를 통해 또는 필드에 직접 종속성을 부여하는 곳입니다. ( Pico 컨테이너 웹 사이트에서 )”

Fabien Potencier는 Dependency Injection 과이를 사용해야하는 필요성에 대한 멋진 기사 시리즈를 작성 했습니다. 그는 또한 내가 정말 사용하고 싶은 Pimple 이라는 멋지고 작은 Dependency Injection Container 를 제공합니다 ( github 에 대한 자세한 정보 ).

위에서 언급했듯이 저는 싱글 톤 사용을 좋아하지 않습니다. 싱글 톤이 좋은 디자인이 아닌 이유에 대한 좋은 요약은 여기 Steve Yegge의 블로그에서 찾을 수 있습니다 .


답변

가장 좋은 방법은 이러한 리소스를위한 일종의 컨테이너 를 보유하는 것입니다 . 이 컨테이너 를 구현하는 가장 일반적인 방법은 다음과 같습니다.

하나씩 일어나는 것

테스트하기 어렵고 글로벌 상태를 의미하므로 권장되지 않습니다. (단일 염)

기재

싱글 톤도 제거하기 때문에 레지스트리도 추천하지 않습니다. (단위 테스트에 어려움)

계승

안타깝게도 PHP에는 다중 상속이 없으므로 모든 것이 체인으로 제한됩니다.

의존성 주입

이것은 더 나은 접근 방식이지만 더 큰 주제입니다.

전통적인

이를 수행하는 가장 간단한 방법은 생성자 또는 setter 주입을 사용하는 것입니다 (setter 또는 클래스 생성자를 사용하여 종속성 개체 전달).

프레임 워크

자신의 의존성 주입기를 굴 리거나 의존성 주입 프레임 워크 중 일부를 사용할 수 있습니다. 야 디프

애플리케이션 리소스

컨테이너 역할을하는 애플리케이션 부트 스트랩에서 각 리소스를 초기화하고 부트 스트랩 객체에 액세스하는 앱의 어느 곳에서나 액세스 할 수 있습니다.

이것은 Zend Framework 1.x에서 구현 된 접근 방식입니다.

리소스 로더

필요할 때만 필요한 리소스를로드 (생성)하는 일종의 정적 개체입니다. 이것은 매우 현명한 접근 방식입니다. 실행중인 것을 볼 수 있습니다. 예를 들어 Symfony의 종속성 주입 컴포넌트 구현

특정 층에 주입

응용 프로그램의 어느 곳에서나 리소스가 항상 필요한 것은 아닙니다. 때로는 컨트롤러 (MV C ) 에서 필요한 경우가 있습니다 . 그런 다음 거기에만 리소스를 주입 할 수 있습니다.

이에 대한 일반적인 접근 방식은 docblock 주석을 사용하여 주입 메타 데이터를 추가하는 것입니다.

여기에 대한 나의 접근 방식을 참조하십시오.

Zend Framework에서 종속성 주입을 사용하는 방법은 무엇입니까? – 스택 오버플로

마지막으로 여기에 매우 중요한 사항 인 캐싱에 대한 메모를 추가하고 싶습니다.
일반적으로 선택한 기술에도 불구하고 리소스가 캐시되는 방식을 생각해야합니다. 캐시는 리소스 자체가됩니다.

응용 프로그램은 매우 클 수 있으며 각 요청에 따라 모든 리소스를로드하는 데 비용이 많이 듭니다. 이 appserver-in-php-Project Hosting on Google Code를 포함하여 많은 접근 방식이 있습니다.


답변

개체를 전역 적으로 사용할 수 있도록 하려면 레지스트리 패턴 이 흥미로울 수 있습니다. 영감을 얻으 려면 Zend Registry를 살펴보세요 .

또한 레지스트리 대 싱글 톤 질문입니다.


답변

단위 테스트에서 보았 듯이 PHP의 객체는 많은 양의 메모리를 차지합니다. 따라서 다른 프로세스를 위해 메모리를 절약하기 위해 가능한빨리 불필요한 개체를 파괴하는 것이 이상적입니다 . 이를 염두에두고 모든 물체가 두 개의 금형 중 하나에 맞습니다.

1) 객체에는 유용한 메서드가 많거나 한 번 이상 호출해야 할 수 있습니다.이 경우 싱글 톤 / 레지스트리를 구현합니다.

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) 객체는 객체를 호출하는 메서드 / 함수의 수명 동안 만 존재합니다.이 경우 단순한 생성은 느린 객체 참조가 객체를 너무 오래 유지하는 것을 방지하는 데 도움이됩니다.

$object = new Class();

임시 개체를 임의의 위치에 저장 하면 나머지 스크립트 동안 개체를 메모리에 유지하는 것에 대한 참조를 잊어 버릴 수 있으므로 메모리 누수가 발생할 수 있습니다.


답변

초기화 된 객체를 반환하는 함수를 사용합니다.

A('Users')->getCurrentUser();

테스트 환경에서는 모형을 반환하도록 정의 할 수 있습니다. debug_backtrace ()를 사용하여 함수를 호출하는 내부를 감지하고 다른 객체를 반환 할 수도 있습니다. 프로그램 내부에서 실제로 진행되는 작업에 대한 통찰력을 얻기 위해 어떤 개체를 얻고 자하는 사람을 내부에 등록 할 수 있습니다.