質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

728閲覧

オブジェクト指向での組み方

BeatStar

総合スコア4958

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

1クリップ

投稿2017/08/07 03:47

C/C++で趣味でやっています。

PCが一度ダメになったので C++用ライブラリを最初から作りなおしています。

Windows API を実装したときに LoadLibrary関数とかで DLLを動的リンクすることありますよね。

その場合の ライブラリを作ろうとしています。

今までは UML風に書くと

[ CDynamicLinkLibrary ] + CONSTRUCTOR( path : const std::string ) throw(例外) // LoadLibraryでリンクし、失敗すれば例外を投げる + DESTRUCTOR() // FreeLibraryで破棄 + Call( funcname : const std::string ) : FARPROC throw(例外) // DLL内の 関数ポインタを取得 + CallCusror( cursorId : int ) : HCURSOR // リソースDLLから 指定IDのカーソルを取得 + CallBitmap( bitmapId : int ) : HBITMAP // リソースDLLから 指定ビットマップを取得 + Play( soundId : int ) : bool // リソースDLLから指定IDの音声を再生する ... - hModule : HMODULE - filename : std::string

としていました。

( つまり、DLLからの取得等は この CDynamicLinkLibrary に任せるっていう... )

ですがPCがダメになったのでその中にあったデータもダメになったため、再度作ることにしました。
これを機に、namespaceに包んだりして もうちょっと C++らしくしようとしています。

で、今イメージしているのは2種類です。

方法1:
namespace LibraryLoader内に CDynamicLinkLibraryクラス ( ただし、DLLのリンクと 破棄, getHandle のみ。構造体にコンストラクタ・デストラクタがついたようなクラス )を追加し、

CFunctionLinkerクラス, CCursorLinkerクラス... という風に処理ごとに分割。( 関数オブジェクト )
それぞれ CDynamicLinkLibraryクラスをコンポジションする。

C++

1// throw(例外) とある部分は std::exception を継承した例外クラスだとします。(例なので) 2namespace LibraryLoader{ 3 4 // cppファイルに書いて隠ぺいする (外部に触らせないために) 5 class CDynamicLinkLibrary{ 6 public: 7 CDynamicLinkLibrary( const std::string filename ) throw(例外); 8 ~CDynamicLinkLibrary(); 9 10 getHandle(void){ return this->hModule; } 11 private: 12 HMODULE hModule; 13 ... 14 }; 15 16 // 以降はヘッダファイルにクラス定義をして外部へ公開する ( 前方宣言付き ) 17 class CFunctionLinker{ 18 public: 19 CFunctionLinker( const std::string filename ) throw(例外){ 20 dll = new CDynamicLinkLibrary( filename ); 21 } 22 23 ~CFunctionLinker(){ delete dll; } 24 25 FARPROC operator()( const std::string funcname ){ 26 HMODULE module = dll->getHandle(); 27 // ここで HMODULE から 関数ポインタを取得する 28 } 29 private: 30 CDynamicLinkLibrary* dll; 31 }; 32 33 class CCursorLinker{ 34 public: 35 CCursorLinker( const std::string filename ) throw(例外){ 36 dll = new CDynamicLinkLibrary( filename ); 37 } 38 39 ~CCursorLinker(){ delete dll; } 40 41 FARPROC operator()( const std::string funcname ){ 42 HMODULE module = dll->getHandle(); 43 // ここで HMODULE から カーソルを取得する 44 } 45 private: 46 CDynamicLinkLibrary* dll; 47 }; 48 ... 49}

方法2:
従来のやり方 ( 上記のUML風のやつ ) を namespace LibraryLoader で包んだだけ。

どれが C++ として正しいのかがわかりません。

動く・動かない ( または コンパイルが通る・通らない ) は実際に試せばいいかもしれませんが、

こういうのは慣れとかだと思うので。

自分なりに デザインパターン ( といっても ちょっとだけ。 Factory, TemplateMethod, Strategy ... となんとなく使えそうなやつだけ ) やったり、

UML を解説してるサイトをなるべく読んだり,

自分なりに組んだりしているのですが...

なぜ 方法1 のように考えたかというと、

2chだったか、何でだったか忘れましたが、「それ オブジェクト指向の原則からしたらダメじゃねーか!」みたいな文章があったので、

「ああ、オブジェクト指向に『原則』ってあるんだな」と思い、

検索したところ、

単一責任の原則 ( SRP原則 ) というらしいですね。

まだ詳しくはチェックしていませんが、

「クラスを変更する理由が2つ以上存在してはならない」というやつらしいです。

それで、そういうことを紹介している記事にちょいちょい「リファクタリング」というものが出てきます。

主に Javaでの説明が多いですが、

リファクタリングしてみよう2の「フィールドの引き上げ(320)」で

フィールドの名前が違う場合、スーパークラスのフィールドとして使いたい名前に変更する コンパイルしてテストする スーパークラスに新しいフィールドを作成する =>フィールドがprivateならば、サブクラスから参照できるようにprotectedに変更する サブクラスのフィールドを削除する コンパイルしてテストする そのフィールドに対して「自己カプセル化フィールド(171)」の適用を検討する

(上記ページより)

とあったので方法1を思いつきました。

ですが こういうのは条件 ( 状況とか ) によって変わりますよね。

やたらめったら Singletonパターンを適用するのはダメだし... とか。

今回の場合はどうやれば(オブジェクト指向的に)正解なのでしょうか。

やるからには出来る限り そういう原則とかを守りたいので。

言語は上記の通り C++ でお願いします。

宜しくお願い致します。

shuntksh👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

こんにちは。

私なら「方法2」を使うと思います。

一部の「意識高い系」の人たちの中には、滅多矢鱈に細かく分割することを好む人達がいます。確かに無制限に巨大なクラスや関数は無駄な依存を増やすのでよくないです。その一点のみに着目しているのだと思います。

クラスや関数を分割すると、当たり前ですがそのクラス宣言や関数宣言、呼び出しI/Fが増加します。これはプログラム全体の複雑さを増しますから、無闇矢鱈に細かく分割するのは、確実に悪手です。私の目には「方法1」はこれに該当しているように見えます。

では、どんな粒度がベストなのか悩ましいですが、ほとんど関連性のない機能を1つのクラスに入れるとか、関数内のネストが深くなりすぎて、見落としが悪くなった等を契機に分割すれば良いように感じます。
最初から予想される機能分割はしておくとよいですが、分割が必要かどうか分からない部分まで「分割しておいた方が安心だから」の理由で分割して細かくしすぎるのは良くないです。

要はバランスです。バランス感覚は経験によって育ちますので、最初から経験豊富な人並みの設計を目指すのは難しいです。
自分なりにベストと感じる粒度で設計していけば良いのでは無いでしょうか?

投稿2017/08/07 04:23

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

何を目的にしているかによると思います。

再利用できればとりあえずいいんじゃないのかなという気がします。
あとは、リファクタリングについても触れているので、単体テスト可能なように抽象クラスを適宜いれるとかになります。

上記のような目的を考慮すると、
HMODULEとかHANDLEとかの資源管理用のクラスとかはWindows APIを使う上ではよく使います。
ですので、CDynamicLinkLibraryみたいなクラスは用意しておいていいと思います。
CFunctionLinkerは要らない気がします。FARPROCという扱いにくいものを返されても使いにくいです。
ある程度種類ごとにAPIをまとめるという感じじゃないでしょうか?

namespace myapplication{ //この階層は具体的なアプリケーションの処理を行うクラスとかを置く。 class MusicPlayer public: void Play(int no); }; class ImageViewer{ public: void Show(int no); }; }//namespace myapplication namespace myapplication{ namespace mylib{ //例えばここに共通で使用するような内部実装を置くとか。 //規模によって階層と分類を増やす //上位クラスをテストしたいのであればAPI呼び出しの抽象クラスを作る //HMODULEの資源管理クラス class HModule{ }; //サウンド関係のAPIとかのラッパー class MusicApi{ }; }//namespace mylib }//namespace myapplication

投稿2017/08/07 15:37

hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

実装したい物について概要しか把握できないので完全に妥当かどうかはわかりませんが、STLのstd::fstreamを参考にクラスを設計するのがいいかと思います。

fstreamのようなクラスの特徴はオブジェクトの生成と消滅がファイル(今回はDLL)の読込と解放に同期することです。二重解放を防止するために、コピーコンストラクタとコピー代入演算子はdeleteしておく必要がありますが、正しくオブジェクトの寿命を管理すれば、DLLの解放忘れもありません。

また、ビットマップ用とカーソル用はそれぞれ継承して作成します。fstreamやifstreamとofstreamにわかれていくのと同じような感じです。そのほうが、親のクラスはDLLの読込の処理に集中できて、子のクラスは必要な差分だけ実装ということができると思います。

STLはC++におけるクラス設計のスタンダードの一つですので、設計の参考にして間違いは無いと思います。

投稿2017/08/07 12:51

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問