OOPは実際に手続き型プログラミングのどのような問題を解決しますか?


17

私は本「C ++ Demystified」を研究しました。今、私はRobert Laforeによる「Turbo C ++のオブジェクト指向プログラミング初版(第1版)」を読み始めました。これらの本を超えるプログラミングの知識はありません。この本は20年前のものなので、時代遅れかもしれません。私は最新版を持っています、私はそれが好きなので古いものを使用しています。主に、ラフォーの本の初版を通してC ++で使用されるOOPの基本概念を研究しています。

Laforeの本は、「OOP」が大きく複雑なプログラムにのみ役立つことを強調しています。すべてのOOP本(同じくLaforeの本)で、手続き型のパラダイムはエラーを起こしやすいと言われています。たとえば、グローバルデータは関数によって簡単に脆弱です。プログラマーは、誤ってデータを破壊する関数を作成するなど、手続き型言語で正直なエラーを犯す可能性があると言われています。

正直に言って、この本に記載されている説明を把握していないため、質問を投稿しています。C++(4版)のオブジェクト指向プログラミングラフォーの本に書かれたこれらのステートメントを把握していません。

オブジェクト指向プログラミングは、プログラミングへの以前のアプローチで制限が発見されたために開発されました....プログラムがますます大きく複雑になるにつれて、構造化プログラミングアプローチでさえ緊張の兆候を示し始めます... ....これらの失敗は、手続き型パラダイム自体に弱点があることを明らかにしています。構造化プログラミングのアプローチがどれほどうまく実装されていても、大規模なプログラムは過度に複雑になります。...関連する2つの問題があります。まず、関数はグローバルデータに無制限にアクセスできます。第二に、手続き型パラダイムの基礎である無関係な関数とデータは、現実世界の貧弱なモデルを提供します...

私はジェフ・ケントによる「dysmystified C ++」という本を研究しました。この本がとても好きで、この本では主に手続き型プログラミングが説明されています。手続き型(構造化)プログラミングが弱い理由がわかりません!

Laforeの本は、いくつかの良い例を使って概念を非常にうまく説明しています。また、OOPは手続き型プログラミングよりも優れているというLaforeの本を読んで、直感を理解しましたが、実際には手続き型プログラミングがOOPよりも弱いことを知りたいと思います。

手続き型プログラミングで直面する実際的な問題とは何か、OOPがプログラミングを容易にする方法を自分で確認したいと思います。私はLaforeの本を黙想的に読むだけで答えが得られると思いますが、手続き型コードの問題を自分の目で見たいです。プログラムのOOPスタイルのコードが、同じプログラムが手続き型パラダイムを使用して記述されていました。

OOPには多くの機能があり、これらのすべての機能が手続き型のコードを記述することによって発生する前述のエラーをどのように除去するかを説明することは不可能だと理解しています。

だから、ここに私の質問があります:

OOPは手続き型プログラミングのどの制限に対処し、実際にこれらの制限を効果的に削除するのですか?

特に、手続き型パラダイムを使用して設計するのは難しいが、OOPを使用して簡単に設計するプログラムの例はありますか?

PS:https : //stackoverflow.com/q/22510004/3429430からクロス投稿


3
手続き型プログラミングとオブジェクト指向プログラミングの違いは、ある程度は表示と強調の問題です。オブジェクト指向として自分自身を宣伝するほとんどの言語も手続き型です。これらの用語は、言語のさまざまな側面を示しています。
ジル 'SO-悪であるのをやめる' 14年

2
質問を再開しました。これがどうなるか見てみましょう。OOPを提示する処理はすべて聖杯であり、プログラミング言語のすべての問題を解決するメシアはナンセンスであることに留意してください。長所と短所があります。あなたはプロを求めていますが、それは公正な質問です。もっと期待しないでください。
ラファエル

OOPを使用せずに(最新の)デスクトップGUIを設計し、その上で成長したいくつかの手法(イベント/リスナーパターンなど)を考えるのは怖いです。しかし、それができないということではありません(確かにできます)。また、作業中のPPの失敗を確認したい場合は、PHPを見て、APIをたとえばRubyと比較してください。
ラファエル

1
また、非常に多くの真のOOPのである「構造化プログラミングのアプローチが実装されているどれだけに関係なく、大規模なプログラムが...過度に複雑になる」が、それは基本的に、より良い複雑さを管理し、開発者により所定の方法で適用された場合...とい大部分は、より良い/よりシャープな範囲の境界/制限によってそれAPIE別名区画システムのようなものを...すなわち:抽象化、ポリモーフィズム、継承、カプセル化
vzn

回答:


9

手続き型言語では、呼び出し元がサポートされている方法でモジュールを使用していることを証明するために必要な制限を必ずしも表現することはできません。コンパイラのチェック可能な制限がない場合は、ドキュメントを作成し、それに従うことを期待し、ユニットテストを使用して目的の使用方法を実証する必要があります。

型の宣言は、最も明白な宣言的制限です(つまり、「xがfloatであることを証明する」)。別の方法として、そのデータ用に設計されていることが知られている関数をデータの変更を強制的に通過させることもできます。プロトコルの実施(メソッド呼び出し順序)は、部分的にサポートされている別の制限、つまり「コンストラクター->他のメソッド*->デストラクター」です。

コンパイラーがパターンを知っている場合、実際の利点(およびいくつかの欠点)もあります。手続き型言語からデータのカプセル化をエミュレートする場合、ポリモーフィック型を使用した静的型付けは少し問題です。例えば:

タイプx1はxのサブタイプ、t1はtのサブタイプ

これは、手続き型言語でデータをカプセル化して、メソッドfとgを持つ型tと、同様に行うサブクラスt1を持つようにする1つの方法です。

t_f(t、x、y、z、...)、t_g(t、x、y、...)t1_f(t1、x、y、z、...)

このコードをそのまま使用するには、呼び出すタイプfを決定する前に、タイプチェックを実行し、tのタイプをオンにする必要があります。次のように回避できます:

タイプt {d:データf:関数g:関数}

そのため、代わりにtf(x、y、z)を呼び出します。メソッドを見つけるためのタイプチェックとスイッチは、各インスタンスにメソッドポインターを明示的に格納するだけで置き換えられます。型ごとに膨大な数の関数がある場合、これは無駄な表現です。その後、tがすべてのメンバー関数を含む変数mを指すような別の戦略を使用できます。この機能が言語の一部である場合、コンパイラーにこのパターンの効率的な表現を作成する方法を理解させることができます。

しかし、データのカプセル化は、可変状態が悪いという認識です。オブジェクト指向ソリューションは、メソッドの背後に隠すことです。理想的には、オブジェクト内のすべてのメソッドの呼び出し順序は明確に定義されています(つまり、コンストラクター->オープン-> [読み取り|書き込み]->クローズ->破棄)。これは「プロトコル」と呼ばれることもあります(研究:「Microsoft Singularity」)。しかし、構築と破壊を超えて、これらの要件は一般に型システムの一部ではありません-または十分に文書化されています。この意味で、オブジェクトはメソッド呼び出しによって遷移される状態マシンの同時インスタンスです。そのため、複数のインスタンスがあり、それらを任意のインターリーブ方式で使用できます。

しかし、可変の共有状態が悪いことを認識すると、オブジェクトのデータ構造は多くのオブジェクトが参照している可変状態であるため、オブジェクト指向が同時実行の問題を引き起こす可能性があることに注意できます。ほとんどのオブジェクト指向言語は呼び出し元のスレッドで実行されます。つまり、メソッド呼び出しに競合状態があります。もちろん、関数呼び出しの非原子シーケンスでは。別の方法として、すべてのオブジェクトがキュー内の非同期メッセージを取得し、オブジェクトのスレッド(プライベートメソッド)でそれらすべてを処理し、メッセージを送信することで呼び出し元に応答します。

マルチスレッドコンテキストでのJavaメソッド呼び出しを、メッセージ(不変の値のみを参照する)を相互に送信するErlangプロセスと比較します。

並列処理と組み合わせた無制限のオブジェクト指向は、ロックのための問題です。ソフトウェアトランザクションメモリ(データベースに似たメモリオブジェクトでのACIDトランザクション)から「通信(不変データ)によるメモリの共有」(関数型プログラミングハイブリッド)アプローチの使用まで、さまざまな手法があります。

私の意見では、オブジェクト指向の文献は、FARを継承に重点を置いており、プロトコル(チェック可能なメソッド呼び出し順序、前提条件、事後条件など)に十分ではありません。オブジェクトが消費する入力は通常、明確に定義された文法を持ち、型として表現できる必要があります。


オブジェクト指向言語では、コンパイラはメソッドが規定の順序で使用されているかどうか、またはモジュールの使用に関するその他の制限を確認できると言っていますか?なぜ「データのカプセル化[...]可変状態が悪いという認識」なのですか?ポリモーフィズムについて話すとき、オブジェクト指向言語を使用していると仮定していますか?
babou 14年

OOで最も重要な機能は、すべてのアクセスがメソッドを通過することを証明するために、データ構造を非表示にできる(つまり、this.xのような参照を要求する)ことです。静的に型付けされたオブジェクト指向言語では、さらに多くの制限を宣言しています(型に基づいて)。メソッドの順序に関する注意は、OOがコンストラクターを最初に、デストラクタを最後に呼び出すことを強制していることだけを言っています。これは、構造的に悪い呼び出し順序を禁止する最初のステップです。簡単な証明は、言語設計の重要な目標です。
ロブ

8

手続き型/関数型プログラミングは、チューリングの議論に入らない場合でも(Ourほど弱くはありません(私の言語にはチューリングの力があり、他のことは何でもできます)。実際、オブジェクト指向の手法は、それらが組み込まれていない言語で最初に実験されました。この意味で、オブジェクト指向プログラミングは、手続き型プログラミングの特定のスタイルにすぎません。しかし、それは、プログラムの理解性と保守に不可欠なモジュール性、抽象化、情報隠蔽などの特定の分野の実施に役立ちます

一部のプログラミングパラダイムは、計算の理論的ビジョンから進化しています。Lispのような言語は、ラムダ計算と言語のメタ循環の概念(自然言語の再帰性に似ています)から発展しました。ホーン節は、プロローグと制約プログラミングの基礎となりました。Algolファミリは、ラムダ計算にも依存していますが、組み込みの反射性はありません。

Lispは興味深い例です。これは、プログラミング言語の革新の多くのテストベッドであり、その二重の遺伝的遺産にまでさかのぼることができます。

しかし、言語は進化し、多くの場合新しい名前が付けられます。進化の主な要因は、プログラミングの実践です。ユーザーは、読みやすさ、保守性、正確性の証明性など、プログラムの特性を改善するプログラミング手法を特定します。次に、プログラムの品質を向上させるために、これらのプラクティスをサポートし、時には実施する機能または制約を言語に追加しようとします。

これが意味することは、これらのプラクティスは古いプログラミング言語ではすでに可能であるということですが、それらを使用するには理解と規律が必要です。特定の構文を持つ主要な概念としてそれらを新しい言語に組み込むことにより、これらのプラクティスは、特にそれほど洗練されていないユーザー(大多数)にとって使いやすく、理解しやすくなります。また、洗練されたユーザーの生活を少し楽にします。

何らかの方法で、サブプログラム/関数/手順がプログラムに対して何であるかを言語設計することです。有用な概念が特定されると、名前(おそらく)と構文が与えられ、その言語で開発されたすべてのプログラムですぐに使用できるようになります。そして、成功すると、将来の言語にも組み込まれます。

例:オブジェクトの向きの再作成

私は今、それを例で説明しようとしています(時間があれば確かにさらに洗練される可能性があります)。この例の目的は、オブジェクト指向プログラムを手続き型プログラミングスタイルで記述できることを示すことではありません。私はむしろことを示すためにしようとするOO施設のない一部の言語は、実際に実際に効果的に模倣するオブジェクト指向のための手段を作成するために、高階関数やデータ構造を使用することができる番組編成に関するその性質から利益を得るために、モジュール性、抽象化と情報隠蔽を含みます

私が言ったように、Lispはオブジェクト指向パラダイムを含む多くの言語進化のテストベッドでした(ただし、最初のオブジェクト指向言語と考えられるのはAlgolファミリーのSimula 67でした)。Lispは非常にシンプルで、その基本的なインタープリターのコードは1ページ未満です。しかし、Lispでオブジェクト指向プログラミングを行うことはできます。必要なのは高階関数だけです。

難解なLisp構文ではなく、擬似コードを使用して、プレゼンテーションを簡素化します。そして、情報の隠蔽モジュール性という単純で本質的な問題を考えます。オブジェクトのクラスを定義し、ユーザーが実装の(ほとんど)にアクセスできないようにします。

ベクトルの追加、ベクトルサイズ、並列処理などのメソッドを使用して、2次元ベクトルを表すvectorというクラスを作成するとします。

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

次に、作成したベクターを実際の関数名に割り当てて使用します。

[ベクトル、xcoord、ycoord、vplus、vsize、vparallel] = vectorclass()

なぜそんなに複雑になるのですか?なぜなら、モジュール性を維持するために、プログラムの残りの部分からは見えないようにvectorrec中間構造を定義できるからです。

極座標で別のコレクションを作成できます

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

しかし、両方の実装を無関心に使用したい場合があります。それを行う1つの方法は、すべての値に型コンポーネントを追加し、同じ環境で上記のすべての関数を定義することです。その後、返された各関数を定義して、最初に座標の型をテストし、次に特定の関数を適用しますそれのための。

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

私が得たもの:特定の関数は(ローカル識別子のスコープのため)見えないままであり、プログラムの残りはvectorclassの呼び出しによって返された最も抽象的なもののみを使用できます。

異議の1つは、プログラム内の各抽象関数を直接定義し、座標型依存関数の定義の中に残すことができるということです。その後、同様に非表示になります。それは事実ですが、各座標タイプのコードは、プログラム全体に散らばった小さな断片にカットされます。

実際、私はそれらに名前を付ける必要さえありません。タイプと関数名を表す文字列によってインデックス付けされたデータ構造に匿名の関数値を保持することができます。関数ベクトルに対してローカルであるこの構造は、プログラムの他の部分からは見えません。

使用を簡単にするために、関数のリストを返す代わりに、明示的に型の値と文字列を引数として取るapplyという単一の関数を返し、適切な型と名前の関数を適用できます。これは、オブジェクト指向クラスのメソッドを呼び出すことによく似ています。

オブジェクト指向機能のこの再構築で、ここで停止します。

私がやろうとしたことは、継承やその他の機能を含む十分に強力な言語で使用可能なオブジェクト指向を構築するのがそれほど難しくないことを示すことです。通訳者のメタサーキュラリティは役立ちますが、大部分は構文レベルであり、無視できるほどではありません。

オブジェクト指向の最初のユーザーは、その方法で概念を実験しました。そして、それは一般に、プログラミング言語の多くの改善にも当てはまります。もちろん、理論的分析にも役割があり、これらの概念を理解または改善するのに役立ちました。

しかし、オブジェクト指向機能を持たない言語が一部のプロジェクトで失敗する運命にあるという考えは、単に保証されていません。必要に応じて、これらの機能の実装を非常に効果的に模倣できます。多くの言語には、オブジェクト指向が組み込まれていない場合でも、オブジェクト指向を非常に効果的に行うための構文的および意味的な力があります。そして、それはチューリングの議論以上のものです。

OOPは他の言語の制限に対処しませんが、より良いプログラムを書くのに役立つプログラミング方法論サポートまたは実施します。したがって、経験の浅いユーザーが、そのサポートなしでより高度なプログラマーが使用および開発したグッドプラクティスに従うことができます。

このすべてを理解するのに良い本は、Abelson&Sussman:コンピュータープログラムの構造と解釈かもしれません。


8

少し歴史があると思います。

1960年代半ばから1970年代半ばの時代は、今日「ソフトウェア危機」として知られています。1972年からの彼のチューリング賞講演で、ダイクストラよりも上手く言えません。

ソフトウェア危機の主な原因は、マシンが数桁強力になったことです!率直に言って、マシンがなければプログラミングはまったく問題ありませんでした。いくつかの弱いコンピューターがあったとき、プログラミングは軽度の問題になり、今では巨大なコンピューターになり、プログラミングも同様に巨大な問題になりました。

これは、最初の32ビットコンピューター、最初の真のマルチプロセッサー、最初の組み込みコンピューターの時代であり、研究者にとって、これらが将来のプログラミングにとって重要になることは明らかでした。顧客の要求が初めてプログラマーの能力を上回ったのは、歴史の中でのことでした。

当然のことながら、プログラミング研究においては非常に肥沃な時期でした。1960年代半ば以前は、LISPとAP / Lがありましたが、「メイン」言語は基本的に手続き型でした:FORTRAN、ALGOL、COBOL、PL / Iなど。1960年代半ばから1970年代半ばにかけて、Logo、Pascal、C、Forth、Smalltalk、Prolog、ML、およびModulaを入手しましたが、SQLやその前身のようなDSLは含まれていません。

また、プログラミング言語を実装するための重要な技術の多くが開発されていた歴史の時代でもありました。この期間に、LR解析、データフロー分析、共通部分式の除去、および特定のコンパイラーの問題(レジスターの割り当てなど)がNP困難であるという最初の認識を得て、そのように対処しようとしました。

これが、OOPが生まれた背景です。したがって、1970年代初頭は、OOPが実際に解決する問題についてのあなたの質問に答えます。最初の答えは、その時代のプログラマーが直面していた多くの問題(現代的および予想される)を解決するように見えたということです。しかし、これはオブジェクト指向が主流になったときではありません。それについてはすぐに説明します。

Alan Kayが「オブジェクト指向」という用語を生み出したとき、彼が念頭に置いていたのは、ソフトウェアシステムが生物学的システムのように構成されるということでした。化学信号(「メッセージ」)に類似した何かを送信することにより相互作用する個々のセル(「オブジェクト」)のようなものがあります。セルの内部をピアリングすることはできませんでした(少なくとも、できませんでした)。シグナリングパスを介してのみ対話します。さらに、必要に応じて、各種類のセルを複数持つことができます。

ここにはいくつかの重要なテーマがあることがわかります。明確に定義されたシグナリングプロトコルの概念(現代の用語、インターフェース)、外部から実装を隠す概念(現代の用語、プライバシー)、および同じタイプの複数の「モノ」が同時にぶらぶらしている(現代の用語では、インスタンス化)。

あなたが気づくかもしれないことの1つは欠落しており、それは継承であり、これには理由があります。

オブジェクト指向プログラミングは抽象概念であり、抽象概念はさまざまなプログラミング言語でさまざまな方法で実装できます。たとえば、「メソッド」の抽象概念は、関数ポインターを使用するC、メンバー関数を使用するC ++、およびメソッドを使用するSmalltalkで実装できます(Smalltalkは抽象概念をほとんど直接実装するため、当然のことです)。これは、(ほぼ)あらゆる言語でOOPを「実行」できることを人々が(まったく正しく)指摘するときの意味です。

一方、継承は、具体的なプログラミング言語の機能です。継承は、OOPシステムの実装に役立ちます。少なくとも、1990年代初期まではそうでした。

1980年代半ばから1990年代半ばまでの時間は、物事が変化した歴史の時間でもありました。この間、安価でユビキタスな32ビットコンピューターが登場したため、企業や多くの家庭では、その日の最下位のメインフレームと同じくらい強力なコンピューターをすべてのデスクに設置する余裕がありました。これは全盛期でもありました。これは、現代のGUIとネットワーク化されたオペレーティングシステムの台頭の時代でもありました。

このような状況で、オブジェクト指向分析とデザインが生まれました。

OOADの影響、「3人のアミーゴ」(Booch、Rumbar、Jacobson)およびその他(Shlaer–Mellorメソッド、責任駆動設計など)の作業は、控えめに言えません。1990年代初期から開発された新しい言語のほとんど(少なくとも、聞いたことがある言語のほとんど)がSimulaスタイルのオブジェクトを持っているのはこのためです。

したがって、1990年代のあなたの質問に対する答えは、ドメイン指向の分析と設計方法論のための(当時の)最高のソリューションをサポートしているということです。

それ以来、ハンマーを持っていたので、それ以降に発生したほとんどすべての問題にOOPを適用しました。OOADとそれが使用したオブジェクトモデルは、アジャイルおよびテスト駆動型開発、クラスターおよびその他の分散システムなどを奨励し、有効にしました。

過去20年間に設計された最新のGUIとオペレーティングシステムは、そのサービスをオブジェクトとして提供する傾向があるため、新しい実用的なプログラミング言語には、少なくとも現在使用しているシステムにバインドする方法が必要です。

したがって、現代の答えは次のとおりです。それは、現代世界とのインターフェースの問題を解決します。現代の世界は、1880年の世界が蒸気で構築されたのと同じ理由でOOPで構築されています。私たちはそれを理解し、それを制御でき、十分にうまく機能します。

もちろん、ここで研究が終了するというわけではありませんが、新しい技術には制限オブジェクトとしてオブジェクト指向が必要であることを強く示しています。オブジェクト指向である必要はありませんが、基本的に互換性がないことはできません。


主なエッセイに入れたくなかった点の1つは、WINP GUIとOOPが非常に自然にフィットしているように見えることです。深い継承階層について多くの悪いことを言うことができますが、これは何らかの意味があるように思われる1つの状況(おそらく唯一の状況)です。
仮名14年

1
OOPは、オペレーティングシステムの内部組織でSimula-67(シミュレーション)に最初に登場しました(Unixの「デバイスクラス」の概念は、本質的にドライバが継承するクラスです)。Parnasの「システムをモジュールに分解する際に使用される基準」、CACM 15:12(1972)、pp。1052-1058、70年代のWirthのModula言語、「abstract data types」はすべて、ある意味での前兆です。その他。
フォンブランド

それはすべて真実ですが、OOPは70年代半ばまで「手続き型プログラミングの問題の解決策」とは見なされていなかったと主張しています。「OOP」の定義は非常に難しいことで有名です。Alan Kayの元々の用語の使用はSimulaのモデルと一致せず、残念なことに世界はSimulaのモデルで標準化しました。一部のオブジェクトモデルにはカレーハワードのような解釈がありますが、Simulaにはありません。ステパノフは、相続が不健全であると指摘したとき、おそらく正しかったでしょう。
仮名

6

なし、本当に。OOPは厳密に言えば、実際には問題を解決しません。非オブジェクト指向システムではできないオブジェクト指向システムでできることは何もありません。実際、チューリングマシンではできなかったことでもできることはありません。最終的にはすべてマシンコードになり、ASMは確かにオブジェクト指向ではありません。

OOPパラダイムは、変数と関数を整理しやすくし、変数と関数をより簡単に移動できるようにします。

Pythonでカードゲームを書きたいとします。カードをどのように表せますか?

OOPについて知らなかった場合は、次のようにできます。

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

カードを手で書くのではなく、これらのカードを生成するためのコードを書くと思いますが、要点はわかります。「1S」はスペードの1を表し、「JD」はダイヤモンドのジャックを表します。ジョーカー用のコードも必要ですが、今のところジョーカーがいないふりをするだけです。

デッキをシャッフルしたいときは、リストを「シャッフル」するだけです。次に、デッキの一番上からカードを取り出すために、リストから一番上のエントリをポップして、文字列を与えます。シンプル。

プレーヤーに表示するためにどのカードを使用しているかを把握したい場合、次のような関数が必要です。

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

少し大きく、長く、非効率ですが、動作します(非常に素朴ですが、それはここのポイントの横にあります)。

カードを画面上で移動できるようにしたい場合はどうしますか?どういうわけか彼らの位置を保存しなければなりません。カードコードの最後に追加することもできますが、それは少し扱いに​​くいかもしれません。代わりに、各カードの場所の別のリストを作成しましょう。

cardpositions=( (1,1), (2,1), (3,1) ...)

次に、リスト内の各カードの位置のインデックスがデッキ内のカード自体のインデックスと同じになるようにコードを記述します。

または、少なくともそうすべきです。間違えない限り。これは、このセットアップを処理するためにコードがかなり複雑になる必要があるためです。カードをシャッフルしたいときは、同じ順序でポジションをシャッフルしなければなりません。カードを完全にデッキから取り出すとどうなりますか?私もその立場を外さなければならず、どこか別の場所に置く必要があります。

また、カードに関するさらに多くの情報を保存したい場合はどうすればよいですか?各カードが裏返されているかどうかを保存する場合はどうなりますか?ある種の物理エンジンが必要で、カードの速度も知る必要がある場合はどうなりますか?各カードのグラフィックを保存するには、完全に別のリストが必要です!そして、これらのすべてのデータポイントについて、それぞれをすべてのデータに何らかの形でマッピングするために、それらをすべて適切に整理するための個別のコードが必要です!

では、これをOOPの方法で試してみましょう。

コードのリストの代わりに、Cardクラスを定義して、そこからCardオブジェクトのリストを作成しましょう。

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

今、突然、すべてがはるかに簡単になりました。カードを移動したい場合、デッキのどこにあるかを把握する必要はありません。それを使用して、位置の配列からその位置を取得します。言わなければなりませんthecard.pos=newpos。メインデッキリストからカードを取り出すとき、他のすべてのデータを保存するために新しいリストを作成する必要はありません。カードオブジェクトが移動すると、そのすべてのプロパティも一緒に移動します。また、反転したときに異なる動作をするカードが必要な場合、メインコードで反転関数を変更してこれらのカードを検出し、異なる動作をする必要はありません。Cardをサブクラス化し、サブクラスのflip()関数を変更するだけです。

しかし、OOなしでは何もできませんでした。オブジェクト指向言語では、言語はあなたのために物事をまとめるための多くの仕事をしているだけです。つまり、間違いを犯す可能性がはるかに低く、コードが短く、読み書きが簡単です。

または、さらに要約すると、OOでは、抽象化のベールの背後にあるデータ処理の一般的な複雑さの多くを隠すことで、より複雑なプログラムと同じ作業を行う単純なプログラムを作成できます。


1
あなたがOOPから取り去る唯一のものが「メモリ管理」であるなら、私はあなたがそれをあまりよく理解しているとは思わない。そこには全体的な設計哲学と「設計によって修正」の寛大なボトルがあります!また、メモリ管理はオブジェクト指向(C ++?)に固有のものではありませんが、その必要性はより顕著になります。
ラファエル

確かに、それは一文バージョンです。また、この用語はかなり非標準的な方法で使用しました。おそらく、「メモリ管理」よりも「情報を処理する」と言う方が良いでしょう。
シルコート14年

関数が何かへのポインターを取得できる非OOP言語、および最初のパラメーターがその同じ種類のものへのポインターである関数へのポインターを許可し、コンパイラーに関数が適切であることを検証させる任意の非OOP言語がありますか渡されたポインタの場合?
supercat 14

3

デバイス、シリアルポート、シリアルポート、ネットワークポート、サーバー間の通信パケットなどを管理する組み込みCを数年間書いた。手続き型プログラミングの経験が少ない訓練を受けた電気技師であり、ハードウェアから自分自身の抽象化を作り出し、それが後になって、普通の人々が「オブジェクト指向プログラミング」と呼ぶものに気づきました。

サーバー側に移動すると、インスタンス化時にメモリ内の各デバイスのオブジェクト表現を設定するファクトリーにさらされました。私は言葉や最初に何が起こっていたのか理解できませんでした-私はこういう名前のファイルに行ってコードを書いたことを知っていました。その後、私は再び、OOPの価値をようやく認識しました。

個人的には、これがオブジェクト指向を教える唯一の方法だと思います。私は入学したばかりのOOP(Java)クラスのイントロを開催しましたが、それは完全に私の頭の上にありました。子猫の分類に基づいて作成されたOOPの説明->猫->哺乳類->生き物->ものまたは葉->枝->木->庭あなたはそれらを問​​題と呼ぶことさえできれば問題...

あまり絶対的ではない用語で見れば、あなたの質問に答える方が簡単だと思います- 「何が解決するのか」ではなく、「ここに問題があり、それがそれをより簡単にする方法」の観点からより多くのことです。私の特定のシリアルポートの場合、静的にシリアルポートを開閉するコードを追加および削除するコンパイル時の#ifdefがたくさんありました。ポートオープン関数はどこでも呼び出され、10万行のOSコードのどこにでも配置でき、定義されていないものを淡色表示しませんでした。頭に入れて 必然的に、反対側のデバイスを想定して、特定のシリアルポートを開こうとするいくつかのタスクが発生する可能性がありますが、作成したコードはどれも機能せず、その理由はわかりません。

抽象化は、まだCにありますが、シリアルポートの「クラス」(まあ、単なる構造データ型)であり、[シリアルポートごとに1つ]の配列があり、[シリアルポートのDMA同等物] 「OpenSerialPortAは」「タスクからハードウェア上で直接呼び出されSetBaudRate」などの機能は、我々はあなたがすべての通信パラメータ(ボーレート、パリティなど)に、合格したことヘルパー関数と呼ばれる最初のものかどうかを確認するために構造体配列を確認しましたポートはすでに開かれていました-もしそうなら、どのタスクによって、それがデバッグprintfとしてあなたに伝えるでしょう、あなたがすぐにあなたが無効にする必要があるコードのセクションにジャンプすることができます-そして、そうでなければ、それは設定に進みましたHALアセンブリ関数を介してすべてのパラメーターを使用し、最終的にポートを開きました。

もちろん、OOPには危険もあります。最終的にそのコードベースをクリーンアップし、すべてをきちんときれいにしたとき、その製品ラインの新しいドライバーを書くことは最終的に計算可能で予測可能な科学でしたが、私のマネージャーは特に彼が必要とするプロジェクトが1つ少なかったので製品をEOLしました管理するために、そして彼は境界線取り外し可能な中間管理でした。それは私から多くのことを取りました/私は非常に落胆することがわかったので、仕事を辞めました。笑。


1
こんにちは!これは、質問に対する答えというよりも、個人の歴史のようです。あなたの例から、オブジェクト指向スタイルで恐ろしいコードを書き直したことがわかりました。しかし、改善がオブジェクト指向に関係していたのか、それとも、それまでにあなたがより熟練したプログラマーだったからなのかは不明です。たとえば、あなたの問題のかなりの部分は、コードがその場所について自由に散らばっていることに起因しているようです。これは、オブジェクトがまったくない手続き型ライブラリを作成することで解決できます。
デビッドリチャービー

2
@DavidRicherbyには手続き型ライブラリがありましたが、それは非推奨です。コードがいたるところにあるだけではありませんでした。ポイントは、これを逆方向に行ったことです。誰も何もOOPしようとしていなかった、それは自然に起こった。
paIncrease

@DavidRicherbyでは、手続き型ライブラリの実装の例を挙げて、同じことについて話していることを確認できますか?
paIncrease

2
ご回答ありがとうございます。+ 1。昔、別の経験豊富なプログラマーが、OOPが彼のプロジェクトをより信頼性の高いものにした方法を説明しました。forums.devshed.com/ programming-42 / ...
user31782

2

OOPプログラミングが、その発明者やユーザーを含む手続き型プログラミングよりも優れている点については、多くの主張意図があります。しかし、単に技術者が設計者によって特定の目的のために設計されたからといって、それらの目的が成功することを保証するものではありません。これは、OOPコーディング革命にもかかわらず、ブルックスの有名なエッセイ「No silver bullet」にまで遡るソフトウェアエンジニアリングの分野における重要な理解です。(新技術については、Gartner Hype Cycleも参照してください。)

両方を使用した人の多くは逸話的な経験からの意見もあり、これにはある程度の価値がありますが、多くの科学研究では自己報告分析が不正確であることが知られています。これらの違いの定量的な分析はほとんど行われていないようです。もしあれば、あまり引用されていません。多くのコンピューター科学者が自分の分野の中心となる特定のトピックについて権威をもって話すが、彼らの見解を裏付ける科学研究を実際に引用しておらず、彼らが実際にその分野従来の知恵を実際に受け継いでいることに気付いていないことは驚くべきことです(広く知られていますが)。

これは科学のサイト/フォーラムであるため、ここでは、より強固な基盤について多数の意見を述べ、実際の違いを定量化する簡単な試みを示します。他の研究があるかもしれませんし、他の人が何か聞いたら指摘するかもしれません。(禅の質問:本当に大きな違いがあり、それを実現するために商用ソフトウェアエンジニアリング分野などで多大な努力が適用/投資されている場合、なぜこれの科学的証拠を手に入れるのが難しいのでしょうか?違いを明確に定量化する分野の古典的で引用の多い参考文献?)

このホワイトペーパーでは、実験的/定量的/ 科学的分析を採用しており、初心者プログラマーによる理解がOOPコーディングメソッドによって改善される場合もありますが、他の場合は(プログラムサイズと比較して)決定的ではありませんでした。これは、OOPの優位性が他の回答やOOP支持者によって進められているという多く/主要な主張の1つにすぎないことに注意してください。この研究では、おそらく「認知的負荷/オーバーヘッド」として知られる心理的要素をコーディング理解度について測定していました。

  • オブジェクト指向プログラムと手続き型プログラムの理解度を、コンピューターと対話する初心者プログラマーが比較します。スーザン・ヴィーデンベック、ベニラ・ラマリンガム、スセエラ・サラサマ、シンシア・L・コリトーレ(1999)

    この論文では、オブジェクト指向および手続き型の初心者による精神表現とプログラム理解を比較する2つの実験について報告します。対象は、オブジェクト指向または手続き型のパラダイムのいずれかを教えたプログラミングの2番目のコースに登録した初心者プログラマーでした。最初の実験では、手続き型およびオブジェクト指向スタイルで書かれた短いプログラムの精神的表現と理解度を比較しました。2番目の実験では、より高度な言語機能を組み込んだより大きなプログラムに研究を拡張しました。短いプログラムの場合、正解した質問の総数に関して2つのグループ間に有意差はありませんでしたが、プログラム指向の質問に対する回答では、オブジェクト指向の科目が手続き科目よりも優れていました。これは、関数情報がプログラムのメンタル表現でより容易に利用可能であったことを示唆し、オブジェクト指向表記法が個々のクラスのレベルで機能を強調するという議論をサポートします。長いプログラムの場合、対応する効果は見つかりませんでした。手続き型の主題の理解度は、あらゆるタイプの質問でオブジェクト指向の主題よりも優れていました。より大きなプログラムで質問に答える際にオブジェクト指向の主題が経験する困難は、彼らが情報を整理し、そこから推論を引き出す際に問題に直面したことを示唆しています。この結果は、オブジェクト指向スタイルの初心者向けのより長い学習曲線と、OOスタイルの機能および特定のOO言語表記法に関連している可能性があることをお勧めします。

こちらもご覧ください:


1
実験的研究に敬意を払っています。しかし、彼らが正しい質問に取り組んでいることを確認する問題があります。OOPと呼ばれるもの、およびその使用方法に含まれる変数が多すぎるため、1つの研究が意味のあるものであるとは言えません。プログラミングの多くのものと同様に、OOPは専門家が独自のニーズを満たすために作成しました。OOPの有用性(OPのトピックとしてではなく、手続き型プログラミングの欠点に対処するかどうか)について議論するとき、次のように尋ねることができます。そして、フィールドスタディだけが完全に意味のあるものになります。
babou

1
逸話の警告:問題が小規模(たとえば、最大500-1000行のコード)である場合、OOPは私の経験に違いをもたらしません。問題が大きく、何らかの形で「交換可能な部分」があり、さらに後で追加する必要がある場合(GUIのウィンドウ、オペレーティングシステムのデバイスなど)、 OOP規律が提供する組織は避けられません。もちろん、言語サポートなしでOOPをプログラミングできます(Linuxカーネルなどを参照)。
フォンブランド

1

注意してください。「オブジェクト指向の概念、データベース、およびアプリケーション」(Kim and Lochovsky、eds)(ACM、1989)のR. King「My Cat is Object-Oriented」の古典を読んでください。「オブジェクト指向」は、明確な概念というよりも流行語になりました。

また、テーマには多くのバリエーションがあり、共通点はほとんどありません。プロトタイプベースの言語(継承はオブジェクトからのものであり、クラス自体はありません)とクラスベースの言語があります。多重継承を許可する言語と、許可しない言語があります。一部の言語には、Javaのインターフェースのようなアイデアがあります(多重継承の骨抜きの形と考えることができます)。ミックスインのアイデアがあります。継承はかなり厳密(C ++のように、サブクラスで取得するものを実際に変更できない)か、非常に自由に処理できます(Perlでは、サブクラスはほとんどすべてを再定義できます)。一部の言語には継承の単一ルートがあり(通常、デフォルトの動作でオブジェクトと呼ばれます)、他の言語ではプログラマーが複数のツリーを作成できます。「すべてがオブジェクトである」と主張する言語もあれば、オブジェクトと非オブジェクトを処理する言語もあり、一部(Javaなど)には「ほとんどのオブジェクトがありますが、これらのいくつかのタイプはそうではありません」。オブジェクトの状態の厳密なカプセル化を主張する言語もあれば、オプションにする(C ++のプライベート、保護、パブリック)言語もありますが、カプセル化をまったく行わない言語もあります。Schemeのような言語を右から見ると、特別な努力なしでOOPが組み込まれていることがわかります(ローカル状態をカプセル化する関数を返す関数を定義できます)。


0

簡潔にするために、オブジェクト指向プログラミングは、手続き型プログラミングに存在するデータセキュリティの問題に対処します。これは、データをカプセル化するという概念を使用して行われ、正当なクラスのみがデータを継承できます。アクセス修飾子は、この目標の達成を促進します。それが役立つことを願っています:)


手続き型プログラミングに存在するデータセキュリティの問題は何ですか?
user31782 14年

手続き型プログラミングでは、グローバル変数の使用を制限することはできません。どの関数でもその値を使用できます。ただし、OOPでは、変数の使用を特定のクラスのみ、またはおそらくそれを継承するクラスのみに制限できます。
マヌ14年

手続き型プログラミングでも、変数を特定の関数に使用することにより、つまりデータをグローバルに宣言しないことにより、グローバル変数の使用を制限できます。
user31782 14年

グローバルに宣言しない場合、グローバル変数ではありません。
マヌー14年

1
「安全」または「正しい」とは、仕様がないことを意味するものではありません。これらは、タイプ、クラス定義、DesignByContractなど、その目標に向けてコードに仕様を設定する試みです。プライベートデータの境界を侵害できないという意味で、「セキュリティ」が得られます。実行するために仮想マシンの命令セットに従う必要があると仮定します。Object Orientationは、メモリを直接読み取ることができる誰かから内部メモリを隠しません。また、オブジェクトプロトコルの設計が悪いと、設計によって秘密が渡されます。
ロブ14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.