これは非オブジェクト指向でどのようにプログラムされますか?[閉まっている]


11

OOPのマイナス面に関する痛烈な記事を読んで、他のパラダイムを支持して、私はあまり過失を見つけることができない例にぶつかった。

私は著者の議論にオープンになりたい、と私は彼らのポイントを理論的に理解することができますが、特に1つの例は、例えばFP言語でそれがより良く実装される方法を想像しようとするのに苦労しています。

From:http : //www.smashcompany.com/technology/object-oriented-programming-is-an-expensive-disaster-which-must-end

// Consider the case where “SimpleProductManager” is a child of
// “ProductManager”:

public class SimpleProductManager implements ProductManager {
    private List products;

    public List getProducts() {
        return products;
    }

    public void increasePrice(int percentage) {
        if (products != null) {
            for (Product product : products) {
                double newPrice = product.getPrice().doubleValue() *
                (100 + percentage)/100;
                product.setPrice(newPrice);
            }
        }
    }

    public void setProducts(List products) {
        this.products = products;
    }
}

// There are 3 behaviors here:

getProducts()

increasePrice()

setProducts()

// Is there any rational reason why these 3 behaviors should be linked to
// the fact that in my data hierarchy I want “SimpleProductManager” to be
// a child of “ProductManager”? I can not think of any. I do not want the
// behavior of my code linked together with my definition of my data-type
// hierarchy, and yet in OOP I have no choice: all methods must go inside
// of a class, and the class declaration is also where I declare my
// data-type hierarchy:

public class SimpleProductManager implements ProductManager

// This is a disaster.

「これらの3つの動作をデータ階層にリンクする合理的な理由はありますか?」に対する著者の議論に対する反論を探していないことに注意してください。

私が具体的に求めているのは、この例をFP言語でモデル化/プログラミングする方法です(理論的には実際のコードではありません)?


44
このような短くて不自然な例のプログラミングパラダイムを比較することは合理的に期待できません。ここで誰もが、特に他の実装が不適切な場合に、自分の好みのパラダイムを他のパラダイムよりも良くするコード要件を思いつくことができます。大きく変化する現実のプロジェクトがある場合にのみ、さまざまなパラダイムの長所と短所に関する洞察を得ることができます。
陶酔

20
オブジェクト指向プログラミングについては、これらの3つのメソッドを同じクラスに一緒に入れることを強制するものではありません。同様に、オブジェクト指向プログラミングについては、データと同じクラスに動作が存在することを要求するものは何もありません。つまり、オブジェクト指向プログラミングでは、データを動作と同じクラスに配置したり、別のエンティティ/モデルに分割したりできます。いずれかの方法は、OOは本当にの概念以来のデータは、オブジェクトに関連しなければならないかについて何も言うことはありませんオブジェクトは基本的にモデリングに関わる行動をクラスに論理的に関連のメソッドをグループ化しています。
ベンコットレル

20
私は記事の暴言に10文を入れて、あきらめました。そのカーテンの後ろの男に注意を払ってはいけません。他のニュースでは、True Scotsmenが主にOOPプログラマーだとは思いもしませんでした。
ロバートハーヴェイ

11
OO言語で手続き型コードを書く人からのもう1つの暴言は、なぜOOが彼のために働いていないのかと疑問に思います。
TheCatWhisperer

11
OOPが最初から最後まで設計ミスの惨事であることは間違いありませんが、その一部であることを誇りに思います!-この記事は読めません。あなたが与える例は、基本的に、設計が不十分なクラス階層は設計が不十分であるという議論を行っています。
エリックリッパー

回答:


42

FPのスタイルでは、Product不変クラスだろうproduct.setPrice変異しないだろうProductオブジェクトを代わりに新しいオブジェクトを返し、increasePrice関数が「スタンドアロン」機能になります。似たような構文(C#/ Javaのような)を使用すると、同等の関数は次のようになります。

 public List increasePrice(List products, int percentage) {
    if (products != null) {
        return products.Select(product => {
                double newPrice = product.getPrice().doubleValue() *
                    (100 + percentage)/100;
                return product.setPrice(newPrice);     
               });
    }
    else return null;
}

ご覧のとおり、ここでのコアは実際には違いはありませんが、不自然なOOPの例の「定型的な」コードが省略されています。しかし、OOPが肥大化したコードにつながる証拠としてこれを見るのではなく、十分に人工的なコード例を構築した場合の証拠としてのみ、何でも証明することが可能です。


7
これを「より多くのFP」にする方法:1)部分関数の代わりに全関数を記述し、高次ヘルパー関数を使用して「if(x!= null)」を抽象化するために、null可能性の代わりにMaybe / Optional型を使用する論理。2)レンズを使用して、製品の価格にレンズのコンテキストでパーセンテージの増加を適用するという点で、単一の製品の価格の増加を定義します。3)部分的なアプリケーション/構成/カリー化を使用して、マップ/選択呼び出しの明示的なラムダを回避します。
ジャック

6
設計上、単に空にするのではなく、nullにすることができるという考えが嫌いだと言わざるを得ません。ネイティブのタプル/コレクションをサポートする関数型言語はそのように動作します。OOPでさえnull、コレクションが戻り型である場所を返すのは嫌です。/暴言
ベリン・ロリッチ

ただし、JavaやC#などのOOP言語のユーティリティクラスのような静的メソッドを使用できます。このコードは部分的に短くなります。リストを渡すことを要求し、自分で保持しないようにするためです。また、元のコードはデータ構造を保持しており、それを移動するだけで、概念を変更することなく元のコードが短くなります。
マーク

@マーク:確かに、OPはすでにこれを知っていると思います。私はこの質問を「機能的な方法でこれを表現する方法」として理解していますが、非OOP言語では必須ではありません。
Doc Brown

@Mark FPとOOはお互いを除外しません。
ピーターB

17

私が具体的に求めているのは、この例をFP言語でモデル化/プログラミングする方法です(理論的には実際のコードではありません)?

「a」FP言語で?十分であれば、Emacs lispを選択します。型(種類、種類)の概念はありますが、組み込みのもののみです。したがって、例は「リスト内の各アイテムに何かを掛けて新しいリストを返す方法」になります。

(mapcar (lambda (x) (* x 2)) '(1 2 3))

行くぞ 他の言語も同様ですが、通常の機能的な「マッチング」セマンティクスで明示的な型の利点を得るという違いがあります。Haskellをご覧ください。

incPrice :: (Num) -> [Num] -> [Num]  
incPrice _ [] = []  
incPrice percentage (x:xs) = x*percentage : incPrice percentage xs  

(または、そのようなもの、それは年齢でした...)

私は著者の議論にオープンになりたい、

どうして?私は記事を読み込もうとしました。私はページの後にあきらめなければならず、残りをすばやくスキャンしました。

この記事の問題は、OOPに反対しているということではありません。私も盲目的に「プロOOP」ではありません。ロジック、関数、OOPパラダイムを使用して、可能な限り同じ言語でプログラミングし、頻繁に3つのいずれかを使用せずに、純粋に命令型、またはアセンブラレベルでプログラミングしました。これらのパラダイムが、あらゆる面で他のパラダイムよりもはるかに優れているとは決して言いません。私は言語 Xの方がYよりも好きだと主張できますか?もちろんそうです!しかし、それはその記事の目的ではありません。

この記事の問題は、彼が最初の文から最後の文まで豊富なレトリックツール(誤り)を使用していることです。それに含まれるすべてのエラーを説明し始めることさえ完全に無駄です。著者は、彼が議論に関心を持たないこと、そして彼が十字軍にいることを豊富に明らかにしている。なぜわざわざ?

結局のところ、これらはすべて仕事を成し遂げるためのツールにすぎません。OOPが優れているジョブがあり、FPが優れている、または両方が過剰である他のジョブがある場合があります。重要なことは、仕事に適したツールを選択し、それを成し遂げることです。


4
「彼が議論に興味を持たないことは明らかで、彼は十字軍に参加している」この宝石に賛成票を投じてください。
陶酔

HaskellコードにNum制約は必要ありませんか?それ以外の場合は(*)を呼び出すことができますか?
jk。

@jk。、私がHaskellをやったのは何年も前のことで、それは彼が探している答えに対するOPの制約を満たすためだけのものでした。;)誰かが私のコードを修正したい場合は、お気軽に。しかし、確かに、Numに切り替えます。
AnoE

7

作者は非常に良い点を指摘し、それからそれをバックアップしようとするつやのない例を選びました。苦情はクラスの実装にあるのではなく、データ階層が関数階層と密接に結びついているという考えにあります。

したがって、著者のポイントを理解するためには、この単一のクラスを機能的なスタイルで実装する方法を確認するだけでは役に立ちません。このクラスの周りのデータと関数のコンテキスト全体を機能的なスタイルでどのように設計するかを見る必要があります。

製品と価格設定に関係する潜在的なデータ型について考えてください。いくつかをブレインストーミングするには:名前、UPCコード、カテゴリ、出荷重量、価格、通貨、割引コード、割引ルール。

これは、オブジェクト指向設計の簡単な部分です。上記の「オブジェクト」すべてのクラスを作成するだけで、私たちは元気ですよね?Productそれらをいくつか組み合わせてクラスを作りますか?

ただし、Set [category]、(discount code-> price)、(quantity-> discount amount)などの種類のコレクションと集計を作成できます。それらはどこに収まりますか?CategoryManagerさまざまな種類のカテゴリをすべて追跡するために個別に作成しますか、それとも、Category作成したクラスにその責任が属しますか?

では、2つの異なるカテゴリの特定の数量のアイテムがある場合に価格割引を提供する機能についてはどうでしょうか。でその行きますProductクラス、Categoryクラス、DiscountRuleクラス、CategoryManagerクラス、または私達は新しい何かが必要なのでしょうか?これが私たちのようなものになる方法DiscountRuleProductCategoryFactoryBuilderです。

関数型コードでは、データ階層は関数と完全に直交しています。セマンティックに意味のある方法で関数をソートできます。たとえば、製品の価格を変更するすべての関数をグループ化できます。その場合mapPrices、次のScalaの例のような一般的な機能を除外することは理にかなっています。

def mapPrices(f: Int => Int)(products: Traversable[Product]): Traversable[Product] =
  products map {x => x.copy(price = f(x.price))}

def increasePrice(percentage: Int)(price: Int): Int =
  price * (percentage + 100) / 100

mapPrices(increasePrice(25))(products)

私はおそらくのように、ここで他の価格に関連する機能を追加することができdecreasePriceapplyBulkDiscountなど

のコレクションも使用するためProducts、OOPバージョンにはそのコレクションを管理するためのメソッドを含める必要がありますが、このモジュールは製品の選択に関するものではなく、価格に関するものにする必要があります。関数とデータの結合により、コレクション管理の定型文もそこに投げ入れられました。

この問題を解決するには、productsメンバーを別のクラスに配置しますが、非常に密結合されたクラスになります。OOプログラマーは、関数とデータの結合を非常に自然で有益だと考えていますが、柔軟性が失われるため、それに関連する高いコストがかかります。関数を作成するときはいつでも、1つのクラスにのみ割り当てる必要があります。関数を使用するときはいつでも、結合データを使用ポイントに到達させる方法を見つける必要があります。これらの制限は巨大です。


2

作成者がF#(「FP言語」)でこのように見えることをほのめかしているように、データと機能を単純に分離します。

module Product =

    type Product = {
        Price : decimal
        ... // other properties not mentioned
    }

    let increasePrice ( percentage : int ) ( product : Product ) : Product =
        let newPrice = ... // calculate

        { product with Price = newPrice }

この方法で、製品のリストで値上げを実行できます。

let percentage = 10
let products : Product list = ...  // load?

products
|> List.map (Product.increasePrice percentage)

注:FPに精通していない場合、すべての関数は値を返します。Cライクな言語から来た場合、関数の最後のステートメントをその前にあるかのように扱うことができますreturn

いくつかの型注釈を含めましたが、それらは不要なはずです。モジュールはデータを所有していないため、ゲッター/セッターはここでは不要です。データの構造と使用可能な操作を所有します。これは、リスト内のすべての要素で関数を実行するようにList公開mapし、新しいリストで結果を返します。

Productモジュールはループについて何も知る必要がないことに注意してください。ループの責任はListモジュール(ループの必要性を作成した人)にあるためです。


1

私は関数型プログラミングの専門家ではないという事実でこれを序文させてください。私はOOPの人です。したがって、以下がFPで同じ種類の機能をどのように達成するかはかなり確信していますが、間違っている可能性があります。

これはIn Typescriptです(したがって、すべての型注釈)。Typescript(javascriptなど)はマルチドメイン言語です。

export class Product extends Object {
    name: string;
    price: number;
    category: string;
}

products: Product[] = [
    new Product( { name: "Tablet", "price": 20.99, category: 'Electronics' } ),
    new Product( { name: "Phone", "price": 500.00, category: 'Electronics' } ),
    new Product( { name: "Car", "price": 13500.00, category: 'Auto' } )
];

// find all electronics and double their price
let newProducts = products
    .filter( ( product: Product ) => product.category === 'Electronics' )
    .map( ( product: Product ) => {
        product.price *= 2;
        return product;
    } );

console.log( newProducts );

詳細に(また、FPの専門家ではありません)、理解しておくべきことは、事前に定義された動作はそれほど多くないということです。もちろんこれはOOPではないため、リスト全体に値上げを適用する「値上げ」メソッドはありません。このような動作を定義するクラスはありません。製品のリストを保存するオブジェクトを作成する代わりに、製品の配列を作成するだけです。その後、標準のFPプロシージャを使用して、この配列を任意の方法で操作できます。特定のアイテムを選択するためのフィルター、内部を調整するためのマップなど。 SimpleProductManagerが提供するAPI。これは、一部の人によって利点と見なされる場合があります。また、あなたがしないことも事実です ProductManagerクラスに関連付けられている荷物について心配する必要はありません。最後に、「SetProducts」や「GetProducts」について心配する必要はありません。製品を隠しているオブジェクトがないためです。代わりに、作業している製品のリストだけがあります。繰り返しますが、これはあなたが話している状況/人によっては、長所または短所かもしれません。また、そもそもクラスが存在しないため、明らかにクラス階層はありません(これは彼が不満を抱いていたことです)。これは、あなたが話している状況/人によっては、長所または短所かもしれません。また、そもそもクラスが存在しないため、明らかにクラス階層はありません(これは彼が不満を抱いていたことです)。これは、あなたが話している状況/人によっては、長所または短所かもしれません。また、そもそもクラスが存在しないため、明らかにクラス階層はありません(これは彼が不満を抱いていたことです)。

私は彼の暴言全体を読むのに時間をかけませんでした。都合の良いときにFPプラクティスを使用しますが、私は間違いなくOOPのような男です。ですから、あなたの質問に答えてから、彼の意見について簡単なコメントをすることも考えました。これは、OOPの「欠点」を強調する非常に不自然な例だと思います。この特定のケースでは、示されている機能については、おそらくOOPが過剰であり、FPがおそらくより適しています。繰り返しになりますが、これがショッピングカートのようなものである場合、製品リストを保護し、アクセスを制限することはプログラムの非常に重要な目標であると思います(FPはそのようなことを実施する方法がありません)。繰り返しになりますが、私はFPの専門家ではないかもしれませんが、eコマースシステム用のショッピングカートを実装しているのであれば、FPよりもOOPを使用する方がはるかに良いでしょう。

個人的には、「Xはただひどい。常にYを使う」という強い主張をする人を真剣に考えるのに苦労しています。プログラミングにはさまざまなツールとパラダイムがあり、解決すべき問題は多種多様です。FPには場所があり、OOPには場所があり、すべてのツールの欠点と利点とそれらをいつ使用するかを理解できなければ、優れたプログラマーになることはできません。

**注:明らかに、私の例には1つのクラスがあります:Productクラスです。この場合、それは単なる愚かなデータコンテナーです。それを使用することはFPの原則に違反するとは思わない。それは型チェックのためのヘルパーです。

**注:私は頭の外を覚えていないし、マップ機能を使用した方法が製品をその場で変更するかどうかを確認しませんでした。アレイ。それは明らかにFPが回避しようとする一種の副作用であり、もう少しコードを追加することで確実にそれが起こらないようにすることができます。


2
これは、古典的な意味でのOOPの例ではありません。真のOOPでは、データは動作と組み合わされます。ここで、2つを分離しました。必ずしも悪いことではありませんが(実際はきれいだと思います)、古典的なOOPとは呼べません。
ロバートハーヴェイ

0

SimpleProductManagerは何かの子(拡張または継承)であるとは思えません。

基本的に、オブジェクトが実行するアクション(動作)を定義するコントラクトであるProductManagerインターフェースの実装です。

それが子である場合(または、言い方をすれば、継承されたクラスまたは別のクラス機能を拡張するクラス)、次のように記述されます。

class SimpleProductManager extends ProductManager {
    ...
}

だから基本的に、著者は言う:

動作がsetProducts、increasePrice、getProductsであるオブジェクトがあります。また、オブジェクトに別の動作があるかどうか、または動作がどのように実装されているかは気にしません。

SimpleProductManagerクラスはそれを実装します。基本的に、アクションを実行します。

また、PercentagePriceIncreaserと呼ばれることもあります。その主な動作は、価格をパーセンテージ値だけ上げることです。

しかし、別のクラスValuePriceIncreaserを実装することもできます。

public void increasePrice(int number) {
    if (products != null) {
        for (Product product : products) {
            double newPrice = product.getPrice() + number;
            product.setPrice(newPrice);
        }
    }
}

外部から見ると、何も変わっていません。インターフェースは同じで、3つのメソッドは同じですが、動作は異なります。

FPにはインターフェースのようなものはないので、実装するのは難しいでしょう。たとえば、Cでは、関数へのポインターを保持し、必要に応じて適切なものを呼び出すことができます。最終的に、OOPでは非常によく似た方法で動作しますが、コンパイラによって「自動化」されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.