OOP哲学
カプセル化、継承、ポリモーフィズム...メソッド、クラスメンバー、プライバシーの分離、抽象化... OOPのトピックに関する記事を表示する頻度と、これらの記事でOOP自体を表示しない頻度。 本物の生きたオブジェクト指向プログラミングは見当たりません。 著者は用語に堪能で、悪名高いOOPの1000の定義を引用することができ、単純な継承を持ついくつかの古典的な例を思い出すことができます。
はい、カプセル化は良いですが、それはOOPの本質ではありません。 他のすべての用語は重要ではありません。 OOPでは、クラスを操作するのが習慣であっても問題ではありません。 実際、「受け入れられた」という言葉は間違っています。 OOPでは、クラス
を操作する
ことができ 、クラス
を操作するのが便利ですが、クラスが存在しないがOOPが存在する場合のアプローチがあります。 どうしてそんなに?
クラスはOOPの最も重要な部分ではありません、それを捨てることは可能ですか、OOPは残っていますか、クラスを取り除きますか? 滞在します。 結局のところ、「クラス」とは何ですか? ツール、洗練されたマルチツール。 そして、OOPとは何ですか? 哲学 相互作用の哲学、プログラマーの思考の哲学、法律と規則、方法と技法、ツールと材料がある哲学。 あなたはミュージシャンをピアノから追い出すことができます-彼はフルートを取ります。 フルートを取る-ミュージシャンは弦を引っ張ってメロディーを描きます。 彼の手を結ぶと、彼はただ口ずさみ始めます。音楽は彼の中にあります。
OOPは音楽のようなものです。 それは理論的に研究することができます:OOPコードの豊富なスコア、自慢のバイオリン抽象クラス、バンドルのノートクラス、フラット相続人、リフレッシュされた実行メソッドのオーバーロード、および特定の効果を達成するためのサイズからサイズへのいくつかの巧妙な移行...いいね 金属フォンの助けを借りて簡単なメロディを作成し、ハンマーで他の人のクラスをノックし、ギターのコードを学びます-そして、標準的なソリューション、標準的なアルゴリズム、標準的なアプローチで簡単なプログラムを組み立てます。 非標準のものが必要ですか? それから-音楽学校へ。 さまざまな楽器を演奏し、それらを正しく組み合わせる方法を学び、デザインパターン、テンプレートクラス、アルゴリズムからの抽象化、情報の非表示などについて学びます。 メロディを作成することも芸術であることに突然気付くでしょう。 あなたの過去の工芸品は無作法で、不定形で、いように見えます。そして、あなたが以前にとてもぎこちなく書いたことに驚くでしょう。 そこでは、クラスを作成し、いくつかの関数をそれに転送する方が良いでしょう。 独自のソリューションを開発するよりも、標準的なソリューションを使用する方が適切です。 しかし、この非自明の回り道マヌーバーは、抽象クラスと3人の単純な相続人によってのみ決定されます...知っています。あなたは正しい道を進んでいます。 OOPは音楽ではなく音楽であることを既に理解しており、調和を学びます。 さて、OOPオーケストラを指揮するか、優れた作曲家になるために、やるべきことはほとんどありません。 そこで音楽を学ぶだけでなく、音楽を感じ、呼吸し、考えます。
And and Orの問題
QtのORMの0.4バージョンでの作業で、興味深いプログラミングの問題に遭遇し、それを美しく解決することができました。 私は自分の考えを他の誰かと共有したかったので、断片的なフレーズでそれらを書き始めました。 すぐに、私はより意味のある文章を取り上げました-プログラマーのこれらのメモが現れました。
私がすでにHabréで
書いた0.3バージョンについての私のQST ORMライブラリでは、複雑なクエリの生成はまだないので、これはまさに今実装したいものです。 ただし、複雑なクエリがある場合は、優先度のために逆アセンブルを試みることができる大きな条件構造もあります。 これらのネストされたANDおよびOR条件はすべて、括弧がなくても、最善の方法を見つけるまで夢中になります。 最初に、1つの小さな単純な条件のクラスが必要であることに気付きました。 それをQstConditionとする-そして何もない、その長い名前。 予備設計段階では、同じことを行います。
最初に、すでにQstConditionクラスを持っている場合、C ++コードをどのように見たいかを紙に書きました。
まず、単純な条件を「プログラム」します。 F1およびF2は、SQLクエリのフィールドです。
F1 = F2
F1 = 5
5 = 5
QstConditionクラスをさまざまな方法でインスタンス化したいと思います。
QstCondition ( "F1" 、FunctorEqual、 "F2" )
QstCondition ( "F1" 、FunctorEqual、QVariant ( 5 ) )
QstCondition ( "F1 = F2" )
QstCondition ( QVariant ( 5 ) 、FunctorEqual、QVariant ( 5 ) )
QstCondition ( "F1 =" 、QVariant ( 5 ) )
私のライブラリにはQstFieldクラスがあります-SQL理解のフィールドの抽象化、QstValue-定数の抽象化。 QstConditionクラスはそれらを理解する必要があります。
QstCondition ( QstField ( "F1" ) 、FunctorEqual、 "F2" )
//ここにネストされた人形があります:QstFieldはQstConditionに渡され、QstValueはQstValueに、QVariantは渡されます。 レコードを少し短くして、QVariant(5)の代わりに5を残すことができます。
QstCondition ( QstField ( "F1" 、QstValue ( QVariant ( 5 ) 、FunctorEqual ) ) )
QstCondition ( QstField ( "F1" ) 、FunctorEqual、QstField ( "F2" ) )
QstCondition ( QstValue ( QVariant ( 5 ) ) 、FunctorEqual、QVariant ( 5 ) )
QstCondition ( QstValue ( QVariant ( 5 ) 、FunctorEqual ) 、QVariant ( 5 ) )
これらすべての場合のコンストラクターは何ですか? 最初の近似では、5つが得られました。
( 1 ) QstCondition ( QString field1、CompareFunctor functor、QString field2 )
( 2 ) QstCondition ( QString field1、CompareFunctor functor、QVariant value )
( 3 ) QstCondition ( QVariant value1、CompareFunctorファンクター、QVariant value2 )
( 4 ) QstCondition ( QString stringCondition )
( 5 ) QstCondition ( QstField qstField1、CompareFunctor functor、QstField qstField2 )
開発にはさらに2つのオプションがあることを既に理解しています。 QstConditionクラス内のどこかに、フィールド名、ファンクター、値があります。 次のようなフィールド名を2行で入力できます。
QstCondition {
QString _fieldName1、_fieldName2 ;
QVariant _value1、_value2 ;
CompareFunctor _functor ;
}
次に、5番目のコンストラクターで何をしますか? QstFieldには「フィールド名」-名前()があり、それを抽出して1行に入れることができます。
QstCondition ( QstField field1、CompareFunctor functor、QstField field2 )
: _fieldName1 ( field1。name ( ) ) 、 //文字列を抽出して挿入します。
_fieldName2 ( field2。name ( ) ) 、
_value1 ( QVariant ( ) ) 、
_value2 ( QVariant ( ) ) 、
_functor (ファンクター)
{ }
私は予測します:これらの非常にテキストの_fieldName1、_fieldName2を取得し、QstFieldに何度も転送してSQLを生成する必要があります。 完成したQstFieldクラスにフィールド名をすぐに保存する方が良いと思いませんか? より論理的かつ意味的に正しい。 QstFieldクラスのオブジェクトの作成/保存/コピーのコストについてはどうですか-わかりません、これは最後に心配します。 特に設計段階で。
次に、QstFieldコンストラクターの1つが次のようになっていることを思い出しました。
QstField ( const QString & name、
const FieldVisibility & visibility = FieldVisible、
const char * columnTitle = "" 、
const int & columnWidth = 0 、
const Qt :: Orientation & titleOrientation = Qt :: Horizontal ) ;
とにかくデータをQstFieldに保存します。つまり、コンストラクター(1)を除外できます。 代わりに、フィールド名として2行だけを渡す場合でも、コンストラクター(5)が呼び出されます。 コンストラクター(1)を残した場合、コンパイラーは私をoldりました:プログラマー、あいまいなコンストラクターがあります! これを確認するために、私は理論から実践に切り替えました。私はQstConditionクラスを作成し、上記のすべてを配置して、いくつかのテストを行いました。 そうです、コンパイラはエラーをスローします、それは私が期待していた場所ではありません:
main.cpp:67:エラー:オーバーロードされた 'QstCondition(const char [3]、Qst :: CompareFunctor、const char [3])'の呼び出しがあいまいです
注:候補は次のとおりです。Qst:: QstCondition :: QstCondition(QVariant、Qst :: CompareFunctor、QVariant)
注:Qst :: QstCondition :: QstCondition(QString、Qst :: CompareFunctor、QVariant)
注:Qst :: QstCondition :: QstCondition(QString、Qst :: CompareFunctor、QString)
行くぞ コンストラクター(1)、(2)、および(3)が競合しています。 熟考した後、クラスを書き直し、同時にパラメーターの順序を変更しました。 デザイナーは次のようになります。
QstCondition ( QString stringCondition ) ;
QstCondition ( QstField field1、QstField field2、CompareFunctor functor ) ;
QstCondition ( QString fieldName1、QString fieldName2、CompareFunctor functor ) ;
QstCondition ( QstValue value1、QstValue value2、CompareFunctor functor ) ;
QstCondition ( QstFieldフィールド、QVariant値、CompareFunctor functor ) ;
QstCondition ( QstFieldフィールド、QstValue値) ;
QstCondition ( QString fieldName、QstValue value、CompareFunctor functor ) ;
ここでは、最も単純なケースが把握されています。 モンスターに移る時です。 「A AND B OR C」の構成におけるブール演算の優先度を思い出し、優先度を気にしたくないことを理解しています。 そのため、このようなSQLクエリの作成を何らかの方法で禁止する必要があります。 ブラケット? はい また、括弧がある場所には、ネストされた条件「A AND(B OR C)」があります。 ターゲット条件をSQLに書き留めました。
F1 = F2 AND(F3 <5 OR F3 IS NULL)
いくつかの仮想クラスQstAndおよびQstOrが役立ちます。
QstAnd ( QstCondition ( "F1 = F2" ) 、
QstOr ( "F3 <5" 、QstCondition ( "F3 IS NULL" ) )
)
設計は巨大ですが、これまでのところ、予備設計段階では何もできません。 ただし、次のようなことができます。
QstCondition ( "F1 = F2" ) 。 および (
QstCondition ( "F3" 、FunctorLess、 5 ) 。 または ( "F3 IS NULL" ) 。 または ( ... ) 。 または ( ... )
) および ( ... ) 。 および ( ... )
この「文字列化」に注意してください。現時点では、ANDとORは等しく、もちろん優先順位を除きます。 つまり、次のオプションの両方が機能するはずです。
QstAnd ( QstCondition ( "F1 = F2" ) 、
QstOr ( "F3 <5" 、QstCondition ( "F3 IS NULL" ) )
)
QstOr ( QstCondition ( "F1 = F2" ) 、
QstAnd ( "F3 <5" 、QstCondition ( "F3 IS NULL" ) )
)
そして、ここに課題があります:これら2つのクラスQstAndとQstOrを作成する方法は? 両方がお互いを「知っている」。 理論的には、C ++でこれが発生した場合、事前定義を試すことができます。
クラス B ;
クラス A
{
//クラスBが使用されます
} ;
クラス B
{
//クラスAが使用されます
} ;
私はこのオプションをすぐに破棄しましたが、うまくいくかどうかさえ考えずに。 QstAndクラスとQstOrクラスは同等であり、考えてみると、これらはまったく同じものであり、最初のものだけが「AND」を生成し、2番目のものは「OR」を生成します。 彼らはお互いのことをできる限り知らず、お互いに依存するべきではありません。 別の状況では、2つのオプション(および/または)がなく、3、5、10、またはそれ以上の同等のクラスがある場合、予定は機能しません。 私は運命の形そのものが好きではありません。ここでは何かが侵害されています。論理的調和、または情報隠蔽の原則です。 私はさらに考え始め、QstAndとQstOrの継承を使用することは基本的であるという結論に達しました。 抽象QstBool祖先クラスを設定し、その仮想メソッドを作成して、2回継承します。
クラス QstBool
{
公開 :
QstBool ( ) ;
virtual〜QstBool ( ) = 0 ;
仮想 QString operatorName ( ) const ;
} ;
クラス QstAnd : パブリック QstBool
{
公開 :
QstAnd ( ) ;
仮想 QString operatorName ( ) const { return "AND" ; } ;
} ;
クラス QstOr : パブリック QstBool
{
公開 :
QstOr ( ) ;
仮想 QString operatorName ( ) const { return "OR" ; } ;
} ;
どんなコンストラクターを呼び出すべきか、すべてがうまく機能しますが、...取得したいものをもう一度見てみましょう:
QstAnd ( QstCondition ( "F1 = F2" ) 、
QstOr ( "F3 <5" 、QstCondition ( "F3 IS NULL" ) )
)
コンストラクタを書きましょう:
QstAnd ( QstCondition condition1、QstOr orCondition2 ) ;
QstAndは、コンストラクターでQstOrオブジェクトを受け取る必要がありますが、これについては何も知りません。 OOPフェイントのこのような標準を作成できます。
QstBool ( QstCondition condition1、QstBool * boolCondition2 ) ;
また、QstAndおよびQstOrクラスのオブジェクトではなく、これらのオブジェクトへのポインターを転送します。 すべてうまくいきますが、残念ながら、前述のオブジェクトにポインターを渡すか、new演算子を使用してこのオブジェクトを作成し、削除されるかどうかを確認する必要があります。
1 ) 。
QstOr myOr1 ( QstCondition ( "F3 <5" ) 、QstCondition ( "F3 IS NULL" ) ) ;
QstAnd ( QstCondition ( "F1 = F2" ) 、
& myOr1 ) ;
2 ) 。
QstAnd ( QstCondition ( "F1 = F2" ) 、
新しい QstOr ( QstCondition ( "F3 <5" ) 、QstCondition ( "F3 IS NULL" ) )
) ;
どちらも私が望んでいたものと一致しません。 継承は、不必要なことを心配させ、すでにかなり大きなコードを重いものにします。
方法はありますか?..私の頭の中ではテンプレートが回っていました。 どこかで私はすでに同様のタスクに会った。 QstOr、QstAnd、およびQstBoolクラスを定型的なものとして考え始めました。 同時に、QstAndとQstOrのどちらも、テンプレート、つまりオプションを再定義するような面倒なことを行うべきではありません
QstAnd <AndStrategy>
そして
QstOr <OrStrategy>
彼らは私にたくさん書くことを余儀なくされたので、落ちました。 しかし、戦略のアイデアは良いように見えました。 2つの簡単なクラスを作成しました。
クラス QstAndOperatorStrategy
{
公開 :
QString operatorName ( ) const
{
「AND」を 返し ます。
}
} ;
クラス QstOrOperatorStrategy
{
公開 :
QString operatorName ( ) const
{
return "OR" ;
}
} ;
次に、それらを使用する必要があります。 どうやって? それは何もうまくいかず、このQstBoolは私を緊張させ、当分の間それを打ち負かすことに決めました。 Strategyテンプレートを使用してクラスを作成しました。
テンプレート < クラス T > クラス BoolTemplate
{
公開 :
QString operatorName ( ) const
{
T t ;
tを返します。 operatorName ( ) ;
} ;
} ;
そうですね。 これからQstAndとQstOrを推測する方法は? 最初にこれを試しました:
typedef BoolTemplate < QstAndOperatorStrategy > QstAnd ;
typedef BoolTemplate < QstOrOperatorStrategy > QstOr ;
すべてがうまくいきますが、繰り返しますが、これらのクラスのそれぞれに暗黙の知識を押し込むことは不可能です。 何かが欠けています。 私は再び行き詰まりました。 私は実験してQstBoolに戻ることにしました。 私が書いたものをコンパイルしないでください、しかしそれは私が望むものを反映しています:
テンプレート < クラス演算子> クラス QstBool
{
公開 :
QstBool ( ) { } ; //デフォルトのコンストラクター
QstBool ( const QstCondition & cond、 const QstAnd & op ) { } ;
QstBool ( const QstCondition & cond、 const QstOr & op ) { } ;
QstBool ( const QstCondition & cond1、 const QstCondition & cond2 ) { } ;
} ;
typedef BoolTemplate < QstAndOperatorStrategy > QstAndTemplate ;
typedef BoolTemplate < QstOrOperatorStrategy > QstOrTemplate ;
typedef QstBool < QstAndTemplate > QstAnd ;
typedef QstBool < QstOrTemplate > QstOr ;
繰り返しになりますが、どうすればQstBoolクラスに渡されるのでしょうか。 合格しないでください! QstAndとQstOrの各コンストラクターで自分自身を取得させます。これはQstBoolであり、事前に定義されています。 私が理解しなければならなかった主なこと:3つのクラスはすべて同じものです。 QstAndとQstOrは同じコインの2つの側面であり、QstBoolはコインそのものです。 両方の戦略をQstBoolに渡すとどうなりますか? 2番目の戦略を追加し、typedefを書き換えます。
テンプレート < クラス Operator1、 クラス Operator2 > クラス QstBool
{
公開 :
QstBool ( ) { } ;
QstBool ( const QstCondition & cond、 const QstAnd & op ) { } ;
QstBool ( const QstCondition & cond、 const QstOr & op ) { } ;
QstBool ( const QstCondition & cond1、 const QstCondition & cond2 ) { } ;
} ;
typedef QstBool < QstAndTemplate、QstOrTemplate > QstAnd ; //最初の戦略とは異なる2番目の戦略を配置します。
typedef QstBool < QstOrTemplate、QstAndTemplate > QstOr ;
だから...私は謎がほとんど落ちているように感じます。 QstAndとQstOrをtypedefにあるものに置き換えて、美しさを得ます:
テンプレート < クラス Operator1、 クラス Operator2 > クラス QstBool
{
公開 :
QstBool ( ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < QstAndTemplate、QstOrTemplate > & op ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < QstOrTemplate、QstAndTemplate > & op ) { } ;
QstBool ( const QstCondition & cond1、 const QstCondition & cond2 ) { } ;
} ;
繰り返しになりますが、QstBoolはクラスQstAndTemplate、QstOrTemplateについて何も知りませんが、結局のところ、それらを戦略として渡します! QstAndTemplateをOperator1に、QstOrTemplateをOperator2に置き換えます。 その結果、コードは次のようになります。
テンプレート < クラス Operator1、 クラス Operator2 >
クラス QstBool
{
公開 :
QstBool ( ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < Operator1、Operator2 > & op ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < Operator2、Operator1 > & op ) { } ;
QstBool ( const QstCondition & cond1、 const QstCondition & cond2 ) { } ;
} ;
私はすぐに例を書きます:
QstAnd andCond ( QstCondition ( "F1 = F2" ) 、
QstOr ( QstCondition ( "F3 = F4" ) 、QstCondition ( "F3 IS NULL" ) ) )) ;
QstOr orCond ( QstCondition ( "F1 = F2" ) 、
QstAnd ( QstCondition ( "F3 = F4" ) 、QstCondition ( "F3 IS NULL" ) ) )) ;
両方とも機能します! 残りはほとんどありません。 オブジェクトが演算子の文字列を返すようにします。 QstBoolに関数を追加します。
QString operatorName ( ) const
{
Operator1 t ;
tを返します。 operatorName ( ) ;
} ;
秘Theは、QstAndではQstAndTemplateがOperator1になり、QstOrではQstOrTemplateになり、関数が必要なものを正確に返すたびになります。 簡単に確認できます。結果を印刷します。
QstAndおよびCond2 ;
QstOr orCond2 ;
qDebug ( ) << andCond2。 operatorName ( ) ; //出力AND
qDebug ( ) << orCond2。 operatorName ( ) ; //出力OR
いいね! だから、リラックスしないでください。 判明したすべてのコードを提供します。
クラス QstAndOperatorStrategy
{
公開 :
QString operatorName ( ) const
{
「AND」を 返し ます。
}
} ;
クラス QstOrOperatorStrategy
{
公開 :
QString operatorName ( ) const
{
return "OR" ;
}
} ;
テンプレート < クラス T > クラス BoolTemplate
{
公開 :
QString operatorName ( ) const
{
T t ;
tを返します。 operatorName ( ) ;
} ;
} ;
テンプレート < クラス Operator1、 クラス Operator2 >
クラス QstBool
{
公開 :
QstBool ( ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < Operator1、Operator2 > & op ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < Operator2、Operator1 > & op ) { } ;
QstBool ( const QstCondition & cond1、 const QstCondition & cond2 ) { } ;
QString operatorName ( ) const
{
Operator1 t ;
tを返します。 operatorName ( ) ;
} ;
} ;
typedef BoolTemplate < QstAndOperatorStrategy > QstAndTemplate ;
typedef BoolTemplate < QstOrOperatorStrategy > QstOrTemplate ;
typedef QstBool < QstAndTemplate、QstOrTemplate > QstAnd ; //最初の戦略とは異なる2番目の戦略を配置します。
typedef QstBool < QstOrTemplate、QstAndTemplate > QstOr ;
よく見てください:BoolTemplate、QstAndTemplate、およびQstOrTemplateクラスは不要です。 この不要なレイヤーを削除します。 次に、2つのコンストラクターについて考えてみましょう。
QstBool ( const QstCondition & cond、 const QstBool < Operator1、Operator2 > & op ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < Operator2、Operator1 > & op ) { } ;
最初の理由により、QstAnd(QstCondition、QstAnd)およびQstOr(QstCondition、QstOr)の形式の構築が可能です。 原則として、F1 AND(F2 AND F3)のようなSQLコードを作成する必要がある場合、それらはあまり干渉しませんが、これにはあまり意味がありません。 最初のコンストラクターを削除します-QstAndクラス内で使用することを禁止します。 QstOrについても同じことが言えます。
テンプレート < クラス Operator1、 クラス Operator2 >
クラス QstBool
{
公開 :
QstBool ( ) { } ;
QstBool ( const QstCondition & cond、 const QstBool < Operator2、Operator1 > & op ) { } ;
QstBool ( const QstCondition & cond1、 const QstCondition & cond2 ) { } ;
QstBool ( const QString & stringCond1、
const QString & stringCond2 ) { } ;
QString operatorName ( ) const
{
Operator1 op1 ;
op1を返します。 operatorName ( ) ;
} ;
} ;
typedef QstBool < QstAndOperatorStrategy、QstOrOperatorStrategy > QstAnd ;
typedef QstBool < QstOrOperatorStrategy、QstAndOperatorStrategy > QstOr ;
したがって、主なタスクは解決され、美しく解決されます。 最後に、もう1つトリックをあげます。 少し前に、「ストリング」を編成することが可能であると推論したことを思い出してください。
QstCondition ( "F1 = F2" ) 。 および (
QstCondition ( "F3" 、FunctorLess、 5 ) 。 または ( "F3 IS NULL" ) 。 または ( ... ) 。 または ( ... )
) および ( ... ) 。 および ( ... )
QstAndおよびQstOrクラスの場合、これらの関数は便利です。 ANDが連結されている場合、ORはすでに無効であり、その逆も同様であることを覚えておく必要があります。 これを行う方法は? 小学校。 戦略クラスに、その操作を担当する1つの関数を追加します。 コンパイラは「and()」関数を誓うので、大文字で関数を書く必要がありました。
クラス QstAndOperatorStrategy
{
公開 :
QstAndOperatorStrategy AND ( const QstCondition & condition )
{
//コード
return * this ;
} ;
QString operatorName ( ) const { return "AND" ; }
} ;
クラス QstOrOperatorStrategy
{
公開 :
QstOrOperatorStrategy OR ( const QstCondition & condition )
{
//コード
return * this ;
} ;
QString