OOPに賛成していくつかの他のパラダイムに賛成しての悲惨な記事を読む私は例に出くわした私はあまり多くの欠点を見つけることができません。
私は著者の主張を受け入れたいと思っています。理論的にはそれらの点は理解できますが、特に1つの例では、FP言語。
// 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言語(実際のコード、理論上ではない))でモデル化/プログラミングするかです。
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は肥大化したコードにつながりますが、十分に人工的なコード例を作成した場合にのみ、何でも証明することが可能です。
私が特に求めているのは、この例をどのように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の方が優れている求人もあれば、両方がやりすぎである可能性もあります。重要なことは、仕事に適したツールで、それを成し遂げましょう。
著者はそれをバックアップするために非常に良い点を作り、それから光沢のない例を選びました。不満はクラスの実装ではなく、データ階層が関数階層と密接に結びついているという考えにあります。
その結果、作者のポイントを理解するために、彼がこの単一のクラスを関数型スタイルでどのように実装するかを確認するだけでは役に立たないということになります。あなたは、彼がこのクラスの周りのデータと関数のコンテキスト全体を機能的なスタイルでどのように設計するかを確認する必要があります。
製品と価格設定に関連する潜在的なデータタイプについて考えてください。名前、upcコード、カテゴリ、配送重量、価格、通貨、割引コード、割引ルールなど、いくつかのブレインストーミングを行います。
これは、オブジェクト指向設計の簡単な部分です。上記の「オブジェクト」のすべてに対してクラスを作成するだけで、問題ありません。 Product
クラスを作成して、それらのいくつかを結合しますか?
しかし、待ってください。これらのタイプのいくつかのコレクションと集計を使用できます:Set [category]、(割引コード->価格)、(数量->割引額)など。それらはどこに収まりますか?別の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)
decreasePrice
、applyBulkDiscount
などの他の価格関連の関数をここに追加できます。
Products
のコレクションも使用しているため、OOPバージョンには、そのコレクションを管理するメソッドを含める必要がありますが、このモジュールを製品の選択に関するものにしたくないので、機能とデータの結合により、コレクション管理の定型文もそこに投入せざるを得なくなりました。
products
メンバーを別のクラスに配置することでこれを解決しようとすることができますが、最終的には非常に密結合されたクラスになります。 OOプログラマーは、関数とデータの結合を非常に自然で有益であると考えていますが、それには高いコストがかかり、柔軟性が失われます。関数を作成するときはいつでも、関数に割り当てる必要がありますクラスは1つだけです。関数を使用したいときはいつでも、その結合データを使用のポイントに到達させる方法を見つける必要があります。これらの制限は非常に大きくなります。
著者が示唆していたように、データと関数を単に分離すると、F#ではこのようになります( "a FP language")。
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モジュールにあるためです。
私が関数型プログラミングの専門家ではないという事実を前置きさせてください。私はより多くのOOP人です。そのため、FPで同じ種類の機能を実現する方法を以下に示しますが、間違いである可能性があります。
これは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おそらく-kill、およびFPはおそらくより適切です。次に、これがショッピングカートのようなものである場合、製品リストを保護してアクセスを制限することは(私が思うに)非常にプログラムの重要な目標であり、FPにはそのようなことを強制する方法がありません。繰り返しますが、私はFPの専門家ではないかもしれませんが、 eコマースシステムにショッピングカートを実装したら、FPよりもOOPを使用します。OOPのカプセル化の原則により、物事が適切に変更されていること、常に有効な状態から別の状態に移行していること、そしてあなたのショッピングカートは常に適切に最新です。
個人的には、「Xはひどい。いつもYを使う」という強い主張をする人を真剣に受け止めるのに苦労しています。プログラミングにはさまざまな問題があるため、プログラミングにはさまざまなツールとパラダイムがあります。 FPがその場にあり、OOPがその場にあり、誰もがすべての欠点と利点を理解できなければ、優れたプログラマーになることはできません。当社のツールとそれらをいつ使用するか。
**注:当然のことながら、私の例にはProductクラスという1つのクラスがあります。この場合、それは単なるばかげたデータコンテナーですが、私の使用はFPの原則に違反するとは思われません。型チェックのヘルパーです。
**注:私は頭のてっぺんを覚えていません。マップ機能を使用した方法で製品がインプレースで変更されるかどうか、つまり元の製品の製品の価格を誤って2倍にしたかどうかは確認しませんでしたアレイ。これは明らかにFPが回避しようとする副作用の一種であり、もう少しコードを追加することで、それが起こらないことを確認できました。
SimpleProductManagerが何かの子(拡張または継承)であるようには見えません。
基本的に、オブジェクトが実行する必要のあるアクション(動作)を定義するコントラクトであるProductManagerインターフェイスの単なる実装です。
それが子である場合(またはより正確に言うと、継承されたクラスまたは別のクラスの機能を拡張するクラス)、次のように記述されます。
class SimpleProductManager extends ProductManager {
...
}
だから基本的に、著者は言う:
Wheには、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の場合は非常によく似た方法で機能しますが、コンパイラによって「自動化」されます。