web-dev-qa-db-ja.com

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

PHPは私の最初のプログラミング言語です。インスタンス化されたオブジェクトに対して静的クラスを使用する場合、頭を完全にラップすることはできません。

オブジェクトを複製および複製できることを理解しています。しかし、私のすべての時間でphpを使用すると、オブジェクトまたは関数は常に単一の戻り値(配列、文字列、int)値またはvoidになりました。

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

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

それはすべて単なるスタイルですか?このようなことを行う適切な方法はありますか?

166
user73119

これは非常に興味深い質問です。答えも興味深いものになるかもしれません^^

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

  • 各オブジェクトが独自にデータを持っているインスタンス化されたクラスを使用します(ユーザーが名前を持っているように)
  • 静的クラスは、他の機能で動作する単なるツールである場合に使用します(たとえば、BBコードをHTMLに変換する構文コンバーターなど。独自の寿命はありません)

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

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

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

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

120
Pascal MARTIN

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

  • 静的メソッドを使用したコードはtestに難しい
  • 静的メソッドを使用するコードはextendが難しい

他のメソッド内で静的メソッドを呼び出すことは、実際にはグローバル変数をインポートするよりも悪いです。 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」を持つシングルトンは、インデックス/メインの配線によって実施されます。シングルトンパターンをnot実装しないクラスのオブジェクトをインスタンス化し、そのインスタンスを必要とする他のインスタンスにのみ渡すように注意します。一方、大文字の「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プロトタイプオブジェクトを模倣するために使用した唯一の状況は、それぞれのフィールドが同じ値を持っていることがわかっている場合です。 -instance。その時点で、静的プロパティと、おそらく静的getter/setterメソッドのペアを使用できます。とにかく、インスタンスメンバーで静的メンバーをオーバーライドする可能性を追加することを忘れないでください。 Zend_Db_Tableのインスタンスで使用されるDBアダプタークラスの名前を指定するためのプロパティです。それらを使用してからしばらく経ちました。

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

68
Ionuț G. Stan

In 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クラスにメソッドを記述し、投稿のコレクションに作用する静的メソッドを記述します。

22
Rafe

それはすべて単なるスタイルですか?

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

14
troelskn

ここでは、特に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で静的になります。そうしない理由が1つある場合を除きます。

12
JG Estiot

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

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

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

それは、より高い凝集力とより低い結合を強制するための完全に便利な方法です。

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

10
grantwparks

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

良い例は、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つのレジストリまたは1つのセッションしか存在しないため、レジストリ変数またはセッション変数を処理するものです。

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

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

そして別の部分で:

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

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

これが、物事を解決するのに役立つ他の回答とともに役立つことを願っています。補足として、「 cohesion 」および「 カップリング 」を確認してください。それらは、すべてのプログラミング言語に適用されるコードを記述するときに使用するいくつかの非常に優れたプラクティスを概説しています。

7
Tres

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

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

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

6

クロス言語アプリケーションでは、静的変数が必要な場合があることは間違いありません。言語を渡すクラス(例:$ _SESSION ['language'])があり、次のように設計された他のクラスにアクセスします。

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

Strings :: getString( "somestring")を使用することは、アプリケーションから言語の使用を抽象化する良い方法です。ただし、この場合、各文字列ファイルに、Stringsクラスによってアクセスされる文字列値を持つ定数が含まれていると、非常にうまく機能します。

3
dudewad

一般に、すべてのインスタンス間で絶対に共有する必要がある場合、またはシングルトンを作成する場合を除き、メンバー変数とメンバー関数を使用する必要があります。メンバーデータとメンバー関数を使用すると、複数の異なるデータに関数を再利用できますが、静的データと関数を使用する場合、操作対象のデータのコピーは1つしか持てません。さらに、PHPには適用できませんが、静的関数とデータはコードを再入不可能にしますが、クラスデータは再入を容易にします。