정적 클래스와 인스턴스화 된 클래스를 사용하는 경우


170

PHP는 제 첫 프로그래밍 언어입니다. 정적 클래스와 인스턴스화 된 객체를 사용할 때 머리를 감쌀 수는 없습니다.

객체를 복제하고 복제 할 수 있다는 것을 알고 있습니다. 그러나 PHP를 사용하는 모든 시간에 객체 또는 함수는 항상 단일 반환 값 (배열, 문자열, 정수) 값 또는 공백으로 끝났습니다.

비디오 게임 캐릭터 클래스와 같은 책의 개념을 이해합니다. 자동차 객체를 복제하고 새로운 것을 빨간색으로 만듭니다. 모두 의미가 있지만 PHP와 웹 응용 프로그램의 응용 프로그램은 아닙니다.

간단한 예입니다. 블로그. 정적 또는 인스턴스화 된 객체로 가장 잘 구현되는 블로그의 객체는 무엇입니까? DB 클래스? 전역 범위에서 db 객체를 인스턴스화하지 않는 이유는 무엇입니까? 왜 모든 객체를 정적으로 만들지 않습니까? 성능은 어떻습니까?

그것은 모두 스타일입니까? 이 일을하는 적절한 방법이 있습니까?

답변:


123

이것은 매우 흥미로운 질문이며 답변도 재미있을 것입니다 ^^

사물을 고려하는 가장 간단한 방법은 다음과 같습니다.

  • 각 객체에 자체 데이터가있는 인스턴스화 된 클래스를 사용합니다 (사용자가 이름을 갖는 것처럼)
  • BB 코드를 HTML로 변환하는 구문 변환기와 같이 다른 것들에서 작동하는 도구 일 때 정적 클래스를 사용하십시오.

(예, 정말 지나치게 단순화했습니다 ...)

정적 메소드 / 클래스에 대한 한 가지 점은 단위 테스트를 촉진하지 않는다는 것입니다 (적어도 PHP에서는 가능하지만 다른 언어에서도 가능).

정적 데이터에 대한 또 다른 점은 프로그램에 인스턴스가 하나만 존재한다는 것입니다. MyClass :: $ myData를 어딘가의 값으로 설정하면이 값을 갖게됩니다. 한 명의 사용자 만 가질 수 있습니다. 그렇게 크지 않습니까?

블로그 시스템의 경우 무엇을 말할 수 있습니까? 정적으로 작성하는 것은 많지 않습니다. 실제로는 생각합니다. 아마도 DB 액세스 클래스 일지 모르지만 아마도 ^^


기본적으로 사진, 사용자, 게시물 등의 고유 한 작업을 할 때 객체를 사용하고 일반적인 일을 할 때 정적을 사용합니까?
Robert Rocha

1
사용자에 대해 잘 말하면 각 요청마다 한 명의 활성 사용자 만 있습니다. 정적 요청의 활성 사용자를 유지하는 것이 의미가 있습니다.
My1

69

정적 메소드 사용에 대한 주요 두 가지 이유는 다음과 같습니다.

  • 정적 메소드를 사용하는 코드는 테스트 하기 어렵다
  • 정적 메소드를 사용하는 코드는 확장 하기 어렵다

다른 메소드 안에 정적 메소드 호출이 있으면 실제로 전역 변수를 가져 오는 것보다 나쁩니다. PHP에서 클래스는 전역 기호이므로 정적 메서드를 호출 할 때마다 전역 기호 (클래스 이름)를 사용합니다. 글로벌이 악한 경우입니다. Zend Framework의 일부 구성 요소에 이러한 종류의 접근 방식에 문제가있었습니다. 객체를 빌드하기 위해 정적 메소드 호출 (공장)을 사용하는 클래스가 있습니다. 맞춤형 객체를 반환하기 위해 해당 인스턴스에 다른 팩토리를 제공하는 것은 불가능했습니다. 이 문제에 대한 해결책은 프로그램 시작시 인스턴스 및 instace 메소드 만 사용하고 싱글 톤 등을 시행하는 것입니다.

Google의 애자일 코치로 일하는 Miško Hevery 는 흥미로운 이론을 가지고 있거나 객체를 사용하는 시간과 객체 생성 시간을 분리해야한다고 조언합니다. 따라서 프로그램의 수명주기는 두 가지로 나뉩니다. main()응용 프로그램의 모든 객체 배선과 실제 작업을 수행하는 부품을 처리하는 첫 번째 부분 ( 방법이라고합시다).

따라서 대신 :

class HttpClient
{
    public function request()
    {
        return HttpResponse::build();
    }
}

우리는 오히려해야합니다 :

class HttpClient
{
    private $httpResponseFactory;

    public function __construct($httpResponseFactory)
    {
        $this->httpResponseFactory = $httpResponseFactory;
    }

    public function request()
    {
        return $this->httpResponseFactory->build();
    }
}

그런 다음 인덱스 / 메인 페이지에서 수행합니다 (이것은 객체 배선 단계 또는 프로그램에서 사용할 인스턴스 그래프를 만드는 시간입니다).

$httpResponseFactory = new HttpResponseFactory;
$httpClient          = new HttpClient($httpResponseFactory);
$httpResponse        = $httpClient->request();

주요 아이디어는 클래스에서 종속성을 분리하는 것입니다. 이렇게하면 코드가 훨씬 확장 가능하고 나에게 가장 중요한 부분 인 테스트 가능합니다. 테스트 가능한 것이 왜 더 중요합니까? 항상 라이브러리 코드를 작성하지는 않기 때문에 확장 성이 그렇게 중요하지는 않지만 리팩토링을 수행 할 때 테스트 가능성이 중요합니다. 어쨌든 테스트 가능한 코드는 일반적으로 확장 가능한 코드를 생성하므로 실제로는 상황이 아닙니다.

Miško Hevery는 또한 싱글 톤과 싱글 톤 (자본 S의 유무에 관계없이)을 명확하게 구분합니다. 차이점은 매우 간단합니다. 소문자 "s"가있는 싱글 톤은 인덱스 / 메인의 배선으로 시행됩니다. Singleton 패턴을 구현 하지 않는 클래스의 객체를 인스턴스화하고 해당 인스턴스를 필요한 다른 인스턴스에만 전달하도록주의하십시오. 반면, 대문자 "S"를 가진 Singleton은 고전적인 (반) 패턴의 구현입니다. 기본적으로 PHP 세계에서 많이 사용하지 않는 위장의 세계. 나는 지금까지 하나를 보지 못했습니다. 모든 클래스에서 단일 DB 연결을 사용하려면 다음과 같이하는 것이 좋습니다.

$db = new DbConnection;

$users    = new UserCollection($db);
$posts    = new PostCollection($db);
$comments = new CommentsCollection($db);

위의 작업을 수행하면 싱글 톤이 있으며 테스트에 모의 또는 스터브를 주입하는 좋은 방법이 있습니다. 놀랍게도 단위 테스트가 더 나은 디자인으로 이어지는 방법입니다. 그러나 테스트를 통해 해당 코드를 사용하는 방식에 대해 생각해야한다고 생각할 때 많은 의미가 있습니다.

/**
 * An example of a test using PHPUnit. The point is to see how easy it is to
 * pass the UserCollection constructor an alternative implementation of
 * DbCollection.
 */
class UserCollection extends PHPUnit_Framework_TestCase
{
    public function testGetAllComments()
    {
        $mockedMethods = array('query');
        $dbMock = $this->getMock('DbConnection', $mockedMethods);
        $dbMock->expects($this->any())
               ->method('query')
               ->will($this->returnValue(array('John', 'George')));

        $userCollection = new UserCollection($dbMock);
        $allUsers       = $userCollection->getAll();

        $this->assertEquals(array('John', 'George'), $allUsers);
    }
}

정적 멤버를 사용하고 PHP 5.3에서 JavaScript 프로토 타입 객체를 흉내 내기 위해 사용했던 유일한 상황은 각 필드가 동일한 값의 교차 인스턴스를 가질 것이라는 것을 알 때입니다. 이 시점에서 정적 속성과 정적 getter / setter 메서드 쌍을 사용할 수 있습니다. 어쨌든 정적 멤버를 인스턴스 멤버로 대체 할 가능성을 추가하는 것을 잊지 마십시오. 예를 들어 Zend Framework는의 인스턴스에 사용 된 DB 어댑터 클래스의 이름을 지정하기 위해 정적 속성을 사용하고 Zend_Db_Table있었습니다. 더 이상 관련이 없을 수 있으므로 사용했던 지 오래되었습니다. 그러나 그것이 제가 기억하는 방식입니다.

정적 속성을 처리하지 않는 정적 메서드는 함수 여야합니다. PHP에는 함수가 있으므로 사용해야합니다.


1
@Ionut, 나는 그 비트를 의심하지 않습니다. 직책 / 역할에도 가치가 있다는 것을 알고 있습니다. 그러나 XP / Agile과 관련된 모든 것과 마찬가지로 실제로 플로피 이름이 있습니다.
Jason Jason

2
"종속성 주입"나는 이것에 대해 지나치게 복잡하게 들리는 용어라고 생각합니다. SO와 Google-land에 많은 정보가 있습니다. "singletons"가 진행되는 한, 여기에 정말로 생각하게 된 것이 있습니다 : slideshare.net/go_oh/… PHP 싱글 톤이 실제로 이해가되지 않는 이유에 대한 이야기. 이것이 오래된 질문에 약간 기여하기를 바랍니다 :)
dudewad

22

따라서 PHP에서 static은 함수 또는 변수에 적용될 수 있습니다. 비 정적 변수는 클래스의 특정 인스턴스에 연결됩니다. 비 정적 메소드는 클래스의 인스턴스에서 작동합니다. 라는 클래스를 만들어 봅시다 BlogPost.

title비 정적 멤버가됩니다. 해당 블로그 게시물의 제목이 포함되어 있습니다. 라는 메소드가있을 수도 있습니다 find_related(). 블로그 포스트 클래스의 특정 인스턴스의 정보가 필요하기 때문에 정적이 아닙니다.

이 클래스는 다음과 같습니다.

class blog_post {
    public $title;
    public $my_dao;

    public function find_related() {
        $this->my_dao->find_all_with_title_words($this->title);
    }
}

반면에 정적 함수를 사용하면 다음과 같은 클래스를 작성할 수 있습니다.

class blog_post_helper {
    public static function find_related($blog_post) {
         // Do stuff.
    }
}

이 경우 함수는 정적이며 특정 블로그 게시물에서 작동하지 않으므로 블로그 게시물을 인수로 전달해야합니다.

기본적으로 이것은 객체 지향 디자인에 관한 질문입니다. 클래스는 시스템에서 명사이며, 그에 작용하는 함수는 동사입니다. 정적 함수는 절차 적입니다. 함수의 객체를 인수로 전달합니다.


업데이트 : 또한 인스턴스 메소드와 정적 메소드 사이에 거의 결정이 없으며 클래스 사용과 연관 배열 사용에 대한 결정은 거의 없다고 덧붙였습니다. 예를 들어 블로깅 앱의 경우 데이터베이스에서 블로그 게시물을 읽고이를 객체로 변환하거나 결과 집합에 그대로두고 연관 배열로 취급합니다. 그런 다음 연관 배열 또는 연관 배열 목록을 인수로 사용하는 함수를 작성합니다.

OO 시나리오에서는 BlogPost개별 게시물에 작용하는 클래스의 메소드를 작성하고 게시물 모음에 작용하는 정적 메소드를 작성합니다.


4
이 답변은 특히 업데이트 한 내용과 함께 매우 좋습니다. 왜냐하면 내가이 질문에 끝났기 때문입니다. "::"vs "$ this"의 기능을 이해하지만, 추가 할 때 DB에서 블로그 배열을 연관 배열로 추출한 예를 들어 보겠습니다. 완전히 새로운 차원을 추가합니다. 또한이 질문의 기본 톤에서.
Gerben Jacobs

이것은 매우 많이 제기되는 PHP 질문 에 대한 가장 실용적인 (이론적으로) 대답 중 하나이며 , 부록은 매우 유용합니다. 나는 이것이 많은 PHP 프로그래머들이 추구하는 답이라고 생각합니다.
ajmedway

14

그것은 모두 스타일입니까?

먼 길입니다. 정적 멤버를 사용하지 않고도 완벽한 객체 지향 프로그램을 작성할 수 있습니다. 실제로 일부 사람들은 정적 멤버가 처음에는 불순물이라고 주장합니다. oop의 초보자로서 정적 멤버를 함께 피하는 것이 좋습니다. 절차 적 스타일이 아닌 객체 지향 으로 글을 쓰도록 지시 합니다.


13

특히 PHP를 사용할 때 대부분의 답변에 다른 접근 방식이 있습니다. 이유가없는 좋은 이유가 없다면 모든 클래스는 정적이어야한다고 생각합니다. "그렇지 않은"이유 중 일부는 다음과 같습니다.

  • 클래스의 여러 인스턴스가 필요합니다
  • 수업을 연장해야합니다
  • 코드 부분은 클래스 변수를 다른 부분과 공유 할 수 없습니다

한 가지 예를 들어 보겠습니다. 모든 PHP 스크립트는 HTML 코드를 생성하므로 내 프레임 워크에는 html writer 클래스가 있습니다. 이렇게하면 단일 클래스에 집중해야하는 특수한 작업이므로 다른 클래스가 HTML을 쓰려고 시도하지 않습니다.

일반적으로 다음과 같이 html 클래스를 사용합니다.

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();

get_buffer ()가 호출 될 때마다 html writer를 사용할 다음 클래스가 알려진 상태에서 시작되도록 모든 것이 재설정됩니다.

모든 정적 클래스에는 클래스를 처음 사용하기 전에 호출 해야하는 init () 함수가 있습니다. 이것은 필요 이상으로 관습에 따른 것입니다.

이 경우 정적 클래스의 대안은 지저분합니다. HTML 작성기의 인스턴스를 관리하기 위해 약간의 HTML을 작성해야하는 모든 클래스를 원하지는 않습니다.

이제 정적 클래스를 사용하지 않을 때의 예를 보여 드리겠습니다. 내 양식 클래스는 텍스트 입력, 드롭 다운 목록 등과 같은 양식 요소 목록을 관리합니다. 일반적으로 다음과 같이 사용됩니다.

$form = new form(stuff here);
$form->add(new text(stuff here));
$form->add(new submit(stuff here));
$form->render(); // Creates the entire form using the html class

정적 클래스를 사용 하여이 작업을 수행 할 수있는 방법은 없습니다. 특히 추가 된 각 클래스의 생성자 중 일부가 많은 작업을 수행한다고 생각하면됩니다. 또한 모든 요소에 대한 상속 체인은 매우 복잡합니다. 정적 클래스를 사용해서는 안되는 명확한 예입니다.

문자열 변환 / 포맷과 같은 대부분의 유틸리티 클래스는 정적 클래스가 될 수있는 좋은 후보입니다. 내 규칙은 간단하다. 왜 안되는지 한 가지 이유가 없다면 PHP에서 모든 것이 정체된다.


아래 코드의 예를 작성할 수 있습니까? "코드의 일부는 다른 변수와 클래스 변수를 공유 할 수 없습니다"#JG Estiot
khurshed alam

10

"다른 메소드 내에 정적 메소드 호출이 있으면 실제로 전역 변수를 가져 오는 것보다 더 나쁩니다." ( "worse"정의) ... 및 "정적 속성을 처리하지 않는 정적 메서드는 함수 여야합니다."

이것들은 모두 꽤 쓸데없는 진술입니다. 주제와 관련된 함수 집합이 있지만 인스턴스 데이터가 완전히 부적절하면 전역 네임 스페이스에서 각각이 아니라 클래스에서 정의해야합니다. PHP5에서 사용할 수있는 메커니즘을 사용하고 있습니다.

  • 이름 충돌을 피하면서 모든 네임 스페이스를 제공하십시오.
  • 다른 개발자가 이미 사용 가능한 것을 더 쉽게 찾을 수 있고 바퀴를 재발 명 할 가능성이 더 적습니다.
  • 마법의 값에 대한 전역 정의 대신 클래스 const를 사용하겠습니다.

그것은 더 높은 응집력과 더 낮은 결합을 강제하는 편리한 방법 일뿐입니다.

그리고 FWIW-적어도 PHP5에는 "정적 클래스"와 같은 것은 없습니다. 메서드와 속성은 정적 일 수 있습니다. 클래스의 인스턴스화를 방지하기 위해 클래스를 추상으로 선언 할 수도 있습니다.


2
"클래스의 인스턴스화를 막기 위해 추상 클래스도 선언 할 수 있습니다." 나는 사람들이 __construct ()를 private 함수로 만드는 것에 대해 읽었지만, 필요한 경우 abstract를 사용할 것입니다.
Kavi Siegel

7

먼저 자신에게 물어보십시오.이 물체는 무엇을 나타낼 것입니까? 객체 인스턴스는 별도의 동적 데이터 세트에서 작동하기에 좋습니다.

좋은 예는 ORM 또는 데이터베이스 추상화 계층입니다. 여러 데이터베이스 연결이있을 수 있습니다.

$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));

이 두 연결은 이제 독립적으로 작동 할 수 있습니다.

$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);

이제이 패키지 / 라이브러리에는 SELECT 문에서 반환 된 특정 행을 나타내는 Db_Row 등의 다른 클래스가있을 수 있습니다. 이 Db_Row 클래스가 정적 클래스 인 경우 하나의 데이터베이스에 하나의 데이터 행만 있다고 가정하고 오브젝트 인스턴스가 수행 할 수있는 작업을 수행 할 수 없습니다. 인스턴스를 사용하면 이제 데이터베이스 수에 제한없이 테이블에 무제한의 행을 가질 수 있습니다. 유일한 제한은 서버 하드웨어입니다;).

예를 들어, Db 오브젝트의 getRows 메소드가 Db_Row 오브젝트의 배열을 리턴하면 이제 각 행에서 서로 독립적으로 조작 할 수 있습니다.

foreach ($someRecordsFromDb1 as $row) {
    // change some values
    $row->someFieldValue = 'I am the value for someFieldValue';
    $row->anotherDbField = 1;

    // now save that record/row
    $row->save();
}

foreach ($someRecordsFromDb2 as $row) {
    // delete a row
    $row->delete();
}

정적 클래스의 좋은 예는 사용자 당 하나의 레지스트리 또는 하나의 세션 만 있기 때문에 레지스트리 변수 또는 세션 변수를 처리하는 것입니다.

응용 프로그램의 한 부분에서 :

Session::set('someVar', 'toThisValue');

그리고 다른 부분에서 :

Session::get('someVar'); // returns 'toThisValue'

세션 당 한 번에 한 명의 사용자 만있을 것이기 때문에 세션에 대한 인스턴스를 작성할 필요는 없습니다.

나는 이것이 다른 것들과 함께 도움이되기를 바랍니다. 참고로 " 응집력 "및 " 커플 링 "을 확인하십시오. 모든 프로그래밍 언어에 적용되는 코드를 작성할 때 사용할 아주 좋은 방법을 간략하게 설명합니다.


6

클래스가 정적 인 경우 (인스턴스가 없기 때문에) 다른 클래스로 객체를 전달할 수 없으므로 모든 클래스가 해당 정적 클래스를 직접 사용하므로 코드가 클래스와 밀접하게 연결되어 있음을 의미합니다 .

긴밀한 결합으로 코드 재사용 성이 떨어지고 취약하며 버그가 발생하기 쉽습니다. 정적 클래스가 클래스의 인스턴스를 다른 클래스로 전달할 수 없도록합니다.

그리고 그렇습니다. 이것은 이미 언급 된 많은 다른 이유 중 하나 일뿐입니다.


3

일반적으로 멤버 변수와 멤버 함수를 모든 인스턴스간에 절대적으로 공유해야하거나 싱글 톤을 작성하지 않는 한 멤버 함수와 멤버 함수를 사용해야합니다. 멤버 데이터 및 멤버 함수를 사용하면 여러 다른 데이터 조각에 함수를 재사용 할 수 있지만 정적 데이터 및 함수를 사용하는 경우 작동하는 데이터 사본은 하나만 가질 수 있습니다. 또한 PHP에는 적용 할 수 없지만 정적 함수와 데이터는 코드를 재진입하지 않는 반면 클래스 데이터는 재진입을 용이하게합니다.


3

언어 간 응용 프로그램에서 정적 변수를 원할 경우가 분명히 있다고 말하고 싶습니다. 언어를 전달하는 클래스 (예 : $ _SESSION [ 'language'])를 가질 수 있으며 다음과 같이 설계된 다른 클래스에 액세스합니다.

Srings.php //The main class to access
StringsENUS.php  //English/US 
StringsESAR.php  //Spanish/Argentina
//...etc

Strings :: getString ( "somestring")을 사용하면 응용 프로그램에서 언어 사용을 추상화 할 수 있습니다. 그러나 원하는 경우이를 수행 할 수 있지만이 경우 각 문자열 파일에 Strings 클래스가 액세스하는 문자열 값이있는 상수가 있으면 꽤 잘 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.