静的クラスとインスタンス化されたクラスを使用する場合


170

PHPは私の最初のプログラミング言語です。静的クラスとインスタンス化されたオブジェクトのどちらを使用するか、頭を悩ませることはできません。

オブジェクトを複製して複製できることを理解しています。しかし、phpを使用している間は常に、オブジェクトまたは関数は常に単一の戻り値(配列、文字列、整数)の値またはvoidとして返されていました。

私はビデオゲームのキャラクタークラスのような本の概念を理解しています。carオブジェクトを複製して新しいものをredにします。これはすべて理にかなっていますが、phpおよびwebアプリでのアプリケーションではありません。

簡単な例。ブログ。ブログのどのオブジェクトが静的またはインスタンス化されたオブジェクトとして実装されるのが最適ですか?DBクラス?グローバルスコープでdbオブジェクトをインスタンス化しないのはなぜですか?代わりにすべてのオブジェクトを静的にしないのはなぜですか?パフォーマンスはどうですか?

それはすべてスタイルだけですか?これを行う適切な方法はありますか?

回答:


123

これは非常に興味深い質問です。回答も興味深いものになる可能性があります^^

物事を考える最も簡単な方法は次のとおりです。

  • 各オブジェクトが独自のデータを持っているインスタンス化されたクラスを使用する(ユーザーが名前を持っているように)
  • 静的クラスを使用するのは、それが他のもので機能するツールだけである場合(たとえば、BBコードからHTMLへの構文コンバーターなど、それ自体には寿命がない場合)

(ええ、私は認めます、本当に過度に単純化された...)

静的メソッド/クラスの1つは、それらがユニットテストを容易にしないことです(少なくともPHPでは、おそらく他の言語でも)。

静的データのもう1つのことは、プログラムにインスタンスが1つしか存在しないことです。MyClass:: $ myDataをどこかにある値に設定した場合、この値はどこにでもあります-ユーザーについて言えば、あなたはたった一人のユーザーしか持つことができません-それは素晴らしいことではありませんか?

ブログシステムの場合、私は何と言えますか?静的なものとして書くことはあまりありませんが、実際にはそう思います。最終的にはDBアクセスクラスかもしれませんが、おそらくそうではありません^^


それで、写真、ユーザー、投稿などのユニークなものを扱うときは基本的にオブジェクトを使用し、一般的なことを意味するときは静的を使用しますか?
Robert Rocha

1
ユーザーについて言えば、リクエストごとにアクティブなユーザーは1人だけです。したがって、リクエストのアクティブユーザーを静的にすることは理にかなっています
My1

69

静的メソッドを使用しない主な2つの理由は次のとおりです。

  • 静的メソッドを使用するコードはテストが難しい
  • 静的メソッドを使用するコードは拡張が難しい

他のメソッド内で静的メソッドを呼び出すことは、実際にはグローバル変数をインポートするよりも悪いです。PHPでは、クラスはグローバルシンボルであるため、静的メソッドを呼び出すたびに、グローバルシンボル(クラス名)に依存します。これは、グローバルが悪である場合です。私は、Zend Frameworkの一部のコンポーネントでこのようなアプローチに問題がありました。オブジェクトを構築するために静的メソッド呼び出し(ファクトリー)を使用するクラスがあります。カスタマイズされたオブジェクトを返すために、そのインスタンスに別のファクトリを供給することは不可能でした。この問題の解決策は、インスタンスとインスタンスメソッドのみを使用し、プログラムの最初にシングルトンなどを適用することです。

MiškoHevery Googleのアジャイルコーチとして働いて、興味深い理論を持っている、というかむしろ我々はオブジェクトを使用する時間からオブジェクトの作成時間を分ける必要があることを、お勧めします。したがって、プログラムのライフサイクルは2つに分割されます。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škoHeveryは、シングルトンとシングルトン(大文字のSの有無にかかわらず)を明確に区別しています。違いは非常に簡単です。小文字「s」のシングルトンは、インデックス/メインの配線によって強制されます。シングルトンパターンを実装していないクラスのオブジェクトをインスタンス化し、そのインスタンスを、それを必要とする他のインスタンスにのみ渡すように注意します。一方、大文字の「S」を持つシングルトンは、古典的な(アンチ)パターンの実装です。基本的に、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プロトタイプオブジェクトを模倣するためにそれらを使用した場合)の静的メンバーは、それぞれのフィールドが同じ値のクロスインスタンスを持つことを知っている場合です。その時点で、静的プロパティを使用できます。また、静的ゲッター/セッターメソッドのペアを使用することもできます。とにかく、インスタンスメンバーで静的メンバーをオーバーライドする可能性を追加することを忘れないでください。たとえば、Zend Frameworkはのインスタンスで使用されるDBアダプタクラスの名前を指定するために静的プロパティを使用していましたZend_Db_Table。私がそれらを使用してからしばらく経っているので、もはや関連性がないかもしれませんが、それは私がそれを覚えている方法です。

静的プロパティを扱わない静的メソッドは関数でなければなりません。PHPには関数があり、それらを使用する必要があります。


1
@Ionut、私はそのことを疑いません。役職にも役割があることに気づきました。ただし、XP / Agileに関連するすべてのものと同様に、非常に不安定な名前が付けられています。
ジェイソン

2
「Dependency Injection」は、このための過度に複雑なサウンド用語だと思います。SOやgoogle-landの至る所でたくさんの本を読んでいます。「シングルトン」に関する限り、これは本当に私に考えさせられたものです:slideshare.net/go_oh/…PHPシングルトンが本当に意味をなさない理由についての話。これが古い質問に少し貢献することを
願ってい

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
この答えは、特にあなたが行った更新に関しては非常に良いものです。それが私がこの質問に終わった理由です。"::"と "$ this"の機能は理解していますが、アカウントに追加すると、連想配列のDBから抽出されたブログ投稿の例を示します。これにより、まったく新しい次元が追加されます。また、この質問の根本的なトーンに。
Gerben Jacobs、2011

これは、このよく尋ねられるPHPの質問に対する最も実用的な(理論的なものに対する)回答の1つです。補遺は非常に役立ちます。これは、多くのPHPプログラマーが求め、賛成している答えだと思います!
ajmedway 2018年

14

それはすべてスタイルだけですか?

長い道のり、はい。静的メンバーを使用することなく、完全に優れたオブジェクト指向プログラムを作成できます。実際、静的メンバーはそもそも不純であると主張する人もいます。私はおっとの初心者として、静的メンバーをすべて一緒に避けようとすることをお勧めします。手続き的なスタイルではなく、オブジェクト指向で書く方向にあなたを強制します。


13

ここでは、特にPHPを使用する場合に、ほとんどの回答に対して異なるアプローチをとっています。正当な理由がない限り、すべてのクラスは静的である必要があると思います。「理由」の理由のいくつかは次のとおりです。

  • クラスの複数のインスタンスが必要です
  • クラスを拡張する必要があります
  • コードの一部は、クラス変数を他の部分と共有できません

一例を挙げましょう。すべてのPHPスクリプトがHTMLコードを生成するため、私のフレームワークにはhtmlライタークラスがあります。これは、単一のクラスに集中する必要のある特殊なタスクであるため、他のクラスがHTMLを書き込もうとしないことを保証します。

通常、次のようなhtmlクラスを使用します。

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

get_buffer()が呼び出されるたびに、HTMLライターを使用する次のクラスが既知の状態で開始されるように、すべてがリセットされます。

すべての静的クラスには、クラスが初めて使用される前に呼び出す必要がある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

「他のメソッドの内部で静的メソッドを呼び出すことは、実際にはグローバル変数をインポートするよりも悪い」(「悪い」の定義)...と「静的プロパティを扱わない静的メソッドは関数でなければなりません」。

これらはどちらもかなり抜本的なステートメントです。主題に関連する一連の関数があり、インスタンスデータが完全に不適切である場合は、グローバルネームスペースではなく、クラスでそれらを定義することをお勧めします。私はPHP5で利用可能なメカニズムを使用して

  • それらにすべて名前空間を与える-名前の衝突を回避する
  • プロジェクト全体に散らばるのではなく、物理的に一緒に配置します。他の開発者は、すでに利用可能なものをより簡単に見つけることができ、ホイールを再発明する可能性が低くなります。
  • マジック値のグローバル定義の代わりにクラスconstを使用させてください

これは、より高い結合力とより低い結合力を強制するための、まったく便利な方法です。

そしてFWIW-少なくともPHP5には、「静的クラス」のようなものはありません。メソッドとプロパティは静的にすることができます。クラスのインスタンス化を防ぐために、クラスを抽象的に宣言することもできます。


2
「クラスのインスタンス化を防ぐために、抽象的に宣言することもできます」-私はそれがとても好きです。以前、__ construct()をプライベート関数にする人々について読みましたが、必要な場合は、抽象を使用することになると思います
Kavi Siegel

7

最初に自問してみてください、このオブジェクトは何を表すのでしょうか?オブジェクトインスタンスは、動的データの個別のセットを操作するのに適しています。

ORMまたはデータベース抽象化レイヤーがその良い例です。複数のデータベース接続がある場合があります。

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

これらの2つの接続は独立して動作できるようになりました。

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

このパッケージ/ライブラリ内に、SELECTステートメントから返された特定の行を表すDb_Rowなどの他のクラスが存在する場合があります。このDb_Rowクラスが静的クラスである場合、それは1つのデータベースに1行のデータしかないため、オブジェクトインスタンスで可能なことを実行できないことを想定しています。インスタンスを使用すると、無制限の数のデータベースの無制限の数のテーブルに、無制限の行を含めることができます。唯一の制限はサーバーハードウェアです;)。

たとえば、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();
}

ユーザーごとにレジストリまたはセッションが1つしかないため、静的クラスの良い例は、レジストリ変数またはセッション変数を処理するものです。

アプリケーションの一部:

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

そして別の部分で:

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

セッションごとに一度に1人のユーザーしか存在しないため、セッションのインスタンスを作成しても意味がありません。

これが、他の答えとともに、物事を明確にするのに役立つことを願っています。補足として、「凝集度」と「カップリング」を確認してください。彼らは、すべてのプログラミング言語に適用されるコードを書くときに使用するいくつかの非常に、非常に良い実践を概説します。


6

クラスが静的である場合、そのオブジェクトを他のクラスに渡すことができないことを意味します(可能なインスタンスがないため)。つまり、すべてのクラスがその静的クラスを直接使用することになります。つまり、コードがクラスと密結合されます。 。

密結合により、コードの再利用性が低下し、壊れやすくなり、バグが発生しやすくなります。静的クラスがクラスのインスタンスを他のクラスに渡すことができないようにする必要があります。

はい、これは他の多くの理由の1つにすぎません。そのいくつかはすでに言及されています。


3

一般に、メンバー変数とメンバー関数は、すべてのインスタンス間で共有する必要がない場合や、シングルトンを作成する場合を除き、使用する必要があります。メンバーデータとメンバー関数を使用すると、関数を複数の異なるデータに再利用できますが、静的データと関数を使用する場合、操作できるデータのコピーは1つだけです。さらに、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.