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

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

ただいまの
回答率

88.37%

オブジェクト指向でクラスを作成する際の考え方について

受付中

回答 7

投稿

  • 評価
  • クリップ 9
  • VIEW 4,352

hirahasu

score 110

いつもお世話になっております。
オブジェクト指向プログラミングについて勉強中の者です。

表題の件ですが、オブジェクト指向プログラミングをするにあたってクラスを作成する時があると思いますが、皆様はクラスを作成する際に、どのような考え方でクラスを作成してますでしょうか?

例えば、じゃんけんゲームを作成するとして私の場合、自分クラス、相手クラス、結果クラスを作成しその中でグー、チョキ、パー等の変数を用意して結果クラスに値を渡して結果を返して表示するというような考え方になります。

じゃんけんゲームというような単純なシステムならともかく、これが大きなシステムを作るとなった時に、どのような考え方でクラスを作成すれば誰が読んでも理解しやすいコードになるか教えていただけると幸いです。

そんなのシステムの仕様によっていくらでも変わってくるという風に思えてきますが、
皆様のクラスを作成する場合はこのように考えてクラスを作成しているという様な事を教えていただければと思います。

よろしくお願いいたします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 7

+6

クラスを作成する場合はこのように考えてクラスを作成しているという様な事を教えていただければ

クラスに限らずですが「そもそも論」なんです。そもそもなぜクラスを作るんでしょう?究極的に言えばそのほうが楽だからです。この楽には将来の楽、他人の楽も含まれます。
問題は未来に何が起こるか分からないことで、これに対しては大きく2つの態度があります。一方には拡張性を高めた、「備えあれば憂いなし」的な態度。オブジェクト指向の教科書には性質上・目的上この方向のものが多いですね(カプセル化、継承による拡張性、…)。もう一方にはYAGNI原則といって、不確定な未来の事を考慮せず、現在確実な要件に集中する態度です。

「クラスを作成する場合」は、その方が楽になると考えている場合で、何をもって楽たりえると思うかは態度や考え方、経験によって変わります。答えは無いので毎回自問するしかありません。この部分が思考停止するとなんでもかんでもクラスにしたり、未来永劫1クラスにしか継承されない基底クラスを作ったり・・するようになります。勉強の目的は手段の追加であって上書きではありません。思考停止さえしなければ良い解には近づいていけると思います。

じゃんけんゲームに戻ると、単純なコンソールで動くじゃんけんなら、私はクラスにしません。めんどくさがり屋なのでエントリーポイントの関数(main)だけで終わらせちゃうかもしれません。でももしMassively MultiplayerでOnlineなじゃんけん(統計的勝負!?)だったら、オンライン中のプレイヤーの数だけインスタンスがあるという設計は最初に検討すると思います。逆にそうしないととってもめんどくさいことになる予感がするんです。

そんな感じで私の場合は「めんどくさい駆動開発」です。「クラスにしないと後々めんどくさそう」と思ったらクラスにします(その閾値は言語によって異なります)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

じゃんけんといっても簡単でない気がします。
いきなり相手クラス・自分クラスでしょうか?

たぶん、ゲームの場(2人以上参加できる場)、プレーヤー(意思決定主体)、ゲーム(参加者全員の勝敗・順位が決まる単位)、じゃんけん(一回一斉に出す勝負)、でその他ディーラーのようなコントロールクラスが必要かもしれません。プレーヤーがじゃんけんで出す手を決める方法が自分だったりネット上の誰かだったり、ボットだったりなんじゃないでしょうか。
じゃんけんのルールとしてグーチョキパーの種類を増やした場合と、それらの組み合わせで勝敗を決める審判のクラスも用意するかもしれません。(5種とか7種のじゃんけんとか世界にはあります。)

柔軟性を確保しつつ、変えた部分が他のクラスへの影響が少ないようにカプセル化していきます。
グーチョキパーが3種なのか5種なのかは、何勝何敗なのかとは関係ないわけですよね。

企業のシステムはむしろゲームより簡単だったりして、在庫だとか注文だとかエンティティの単位は、業務のドキュメントで規定されているので、それに従うことが多いかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

データの取り扱いという側面から考えた場合、カプセル化という考え方からクラスを切り出せると思います。
また処理のありようとしてはデザインパターンという考え方から処理の抽象化や実装のありようを探ることもできそうです。

例えば、じゃんけんゲームを作成するとして

私の場合ならPlayerクラス、Refereeクラスで作成すると思います。

class Player{
  public static final int ROCK = 0;
  public static final int PAPER = 1;
  public static final int SCISSORS = 2;
  private int rps = -1;
  public Player(int rps){
    // 自分で考えた手でPlayerインスタンスを初期化
  }
  public void makeAGuess(){
    // ランダムで手を考えるメソッド(PC側が使用)
  }
  public int open(){
    // 手を公開するメソッド
    return this.rps;
  }
}
class Referee{
  public static void main(String[] args){
    int input = -1;
    // 標準入力から「私」の手を入力
    Player a = new Player(input);
    // 「相手」の手をランダムで生成
    Player b = new Player(0);
    b.makeAGuess();
    // 判定
    Referee r = new Referee();
    Player winner = r.getWinner(a, b);
    if(winner == null){ /* draw! */ }
    if(winner.equals(a)){ /* you win! */ }
    else{ /* you lose! */ }
  }
  public Player getWinner(Player a, Player b){
    int result = (a.rps + 3 - b.rps) % 3;
    if(result == 1) return a;
    if(result == 2) return b;
    return null;
  }
}


冒頭の例でいえば「カプセル化」を考えた書き方ですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

こんにちは。

クラスを作成する際に、どのような考え方でクラスを作成してますでしょうか?

経験上、プログラムを設計する際、可能な範囲でリアル世界と同じ構造にしておくと、スムーズに開発できます。リアルとプログラムが似た構造であれば、リアル上の要求の変化をプログラムへ反映しやすく、将来の拡張も容易なことが多いです。

つまり、リアルに存在する「オブジェクト」の性質をプログラムの「クラス」で表現し、リアル・オブジェクトが存在する姿に合わせて、クラスのインスタンスを生成するようなプログラムにしています。


じゃんけんゲームのような場合、たぶん、プレイヤー・クラスを基底クラスとし、自分クラスやコンピュータ・クラスを派生させるでしょう。ゲームの仕様によってはネットの向こうにいる対戦相手クラスを派生させることもあるかも知れません。
実際のじゃんけんでは普通は審判を各プレイヤーが担いますが、リアルの場合でも矛盾しない判定(後出し等の判定)を必要とするなら審判を別途設けることもあります。それと同様に審判機能を担う「ゲームマスター」クラスを作るだろうと思います。(その方が簡単ですし、各プレイヤーが判定することで、場合によっては喧嘩するところまでモデル化する必要はまずないですから。)
その「ゲームマスター」クラスが、各基底クラスのプレイヤー・クラスとI/Fしてじゃんけんゲームを進行させる感じですね。
もし、結果を記録に残す必要があるようなら、記録係の仕事を担うログ・クラスを作ります。

さて、「結果」はリアルでも存在しますが、「結果」が主体的に動いて何かをなすことはないですね。ですので、結果クラスは作らないだろうと思います。
なお、結果が複数の値を持っている場合は、結果構造体にてやり取りすると思います。リアルの場合は記録紙に当たるイメージです。
構造体は、メンバ変数は原則publicでコンストラクタ以外のメソッドを積極的に持たせることはないようなものをイメージしてます。オブジェクト指向の「オフジェクト」には当たらないですが、必要に応じて設計します。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/06 19:22

    構造体とクラスって区別する必要あるんでしょうか?C++だとデフォルトのアクセス制限以外全く同じですし、C#だと値型であるという違いがあるけど普通にメソッド定義できますし、結局、それってクラスの一種と言ってもいいものなんでは…

    キャンセル

  • 2016/05/06 22:34

    raccyさん。

    構造体とクラスは言語仕様的に事実上同じ言語もありますが、この2つは使用目的が異なると考えています。
    構造体はどちらかというとデータを保持することを目的とし、自律的に機能を提供するというよりは、受動的にデータ保持を行うイメージです。C言語の構造体をイメージして頂ければぴったりです。
    メッセージをやり取りして動作する、派生して機能を拡張する、ポリモーフィックに使う等オブジェクト指向的に使う時に、「構造体」はちょっと違うかなと思います。

    このような使い分けをしている人は少なくないようです。(https://teratail.com/questions/21279)
    raccyさん自身からも、「構造体は全く別枠で考えています。」とのご意見を頂いてますよ。

    キャンセル

  • 2016/05/06 22:50

    > Chironianさん
    そ、そ、そのraccyはきっと偽者に違いないっす(-д-;
    いや、俺が偽者という可能性も…

    まぁ、私も構造体は使用目的が違うってのは同意です。私もC++でstructって書くときはCのように使うときしか使いませんから…

    今回の主題になっている質問を「クラス」=「型」という感じで私の方が捉えちゃったので、ちょっと勘違いかも知れません。オブジェクト指向って、別に全てのクラスにメソッドがある必要も、多態性がある必要も無くて、オブジェクト中心ならオブジェクト指向なんじゃ無いか、むしろ、全部オブジェクトとして考えた方が楽なんじゃ無いかと思ったりしています(きっとRubyに洗脳されたせいだと思われます)。クラスを広い意味で、つまり型という意味でとらえたら、全部クラスと言ってもいいんじゃないかと。でも、そうしたら、クラス云々を話す必要がなくなりますね。あれ、何言っているんだろ?

    キャンセル

  • 2016/05/06 23:28

    > そ、そ、そのraccyはきっと偽者に違いないっす(-д-;
    > いや、俺が偽者という可能性も…

    偽者が多すぎて誰が本物か判らなくなってしまったようですね。
    なるほど、さすがraccyさん。奥が深い。

    キャンセル

+1

私も勉強中の者ですが、私なりに学んだ考え方でジャンケンを考えてみました。

シンプルな問題なので、
そもそもオブジェクト指向で考える必要はないという議論はあると思いますが、
具体的な考え方をお話するために、ここはあえて冗長に考えました。
長文になってしまい、すみません…。


まず、ジャンケンをざっくりと考えてみます。

  • ジャンケンはゲームです。
  • そのゲームには参加者が複数います。
  • 参加者はジャンケンの手を1つ同時に出します。
  • 出した手の強さを競います。
  • 手の強さはグー>チョキ>パー>グー。

これらの情報から抽象的な情報と具体的な情報を切り離しつつ、細かく考えます。
似ている事はまとめます。変わりそうな事は切り離します。

【抽象的な情報】

  • ゲームには参加者が複数必要です。
  • ゲームにはルールが必要です。
  • ゲームは開始されます。
  • 参加者は手を出せます。
  • 出せる手はルールによって決められます。
  • 出した手はルールによって結果が判定されます。
  • 結果は参加者に知らされます。

【具体的な情報】

  • ジャンケンのルール。 
    出せる手はグーチョキパーです。
    手の強さはグー>チョキ>パー>グーです。
    強い手を出した参加者は勝ちという結果になります。
    勝ちが決められないときは引き分けという結果になります。
  • (ジャンケンという)ゲームの流れ。
    参加者は同時に手を出します。
    出された手はルールによって勝敗が決められます。
    参加者は勝敗を知ります。
    必要なら勝敗が決まるまで繰り返します。
  • (ジャンケンする)参加者。
    参加者は人間だったりコンピュータだったりします。

ここで、抽象的な情報をインターフェースにします。

  • ゲーム→IGame
  • ルール→IRule
  • 参加者→IPlayer
  • 手(原因)→ICause
  • 結果→IResult

これらの関係を、インターフェースのメソッドで示します。

  • ゲームは参加者が複数必要です。→void IGame.joinPlayers(IPlayer)
  • ゲームにはルールが必要です。→void IGame.followRule(IRule)
  • ゲームは開始されます。→void IGame.start()
  • 参加者は手を出せます。→ICause IPlayer.serveCause()
  • 出せる手はルールによって決められます。→void IRule.valid(ICause)
  • 出した手はルールによって結果が判定されます。→IResult IRule.match(ICause, ICause)
  • 結果は参加者に知らされます。→void IPlayer.result(IResult)

具体的な情報は、これらインターフェースを実装したクラスになります。

  • ジャンケンのルール。→JankenRule
  • (ジャンケンという)ゲームの流れ。→OnceGame、DrawLoopGame
  • (ジャンケンする)参加者。→CauseStrategiedPlayer
    ここでは参加者から、人間やコンピュータという具体性を、戦略として抽象化し分離しています。
    →JankenCauseAtRandom、JankenCauseStdin

あとはひたすら実装します。
この時、切り離した方が良さそうな事が現れたりしたら、その都度対応します。

今は考慮していませんが、
例えば、3種ジャンケンじゃなくて、5種ジャンケンが必要であれば、
手とルールと戦略のクラスを追加で作ればよいかと思います。ゲームや参加者は再利用できます。
ただ、5種ジャンケンが3種ジャンケンのように「3すくみ」のようなルールであれば、
ジャンケンのルールである「すくみ」から、種類を抽象的な情報として分離すれば、
すくみルールとして再利用できます。


実際に組んだC#のソースです(250行ぐらい)。コンソールアプリケーションとして一応動くはずです。
エラーチェック等はガッツリ省いてます。
行数を削ろうと変な書き方になっているところはご容赦ください…。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {

        IGame game;
        IRule rule = new JankenRule();
        List<IPlayer> players = new List<IPlayer>(){
            new CauseStrategiedPlayer(new JankenCauseStdin(), "You")
            ,new CauseStrategiedPlayer(new JankenCauseAtRandom(), "ComA")
            ,new CauseStrategiedPlayer(new JankenCauseAtRandom(), "ComB")
            ,new CauseStrategiedPlayer(new JankenCauseAtRandom(), "ComC")
            ,new CauseStrategiedPlayer(new JankenCauseAtRandom(), "ComD")
        };

        System.Console.WriteLine("--ジャンケン1回--");
        game = new OnceGame();
        game.followRule(rule);
        game.joinPlayers(players);
        game.start();

        System.Console.WriteLine("--ジャンケン終わるまで--");
        game = new DrawLoopGame(new OnceGame());
        game.followRule(rule);
        game.joinPlayers(players);
        game.start();

        Console.Write("何かキーを押したら終わります。");
        Console.ReadKey();
    }
}

/////////////////////////////インターフェース/////////////////////////////

//ゲームの流れ。
public interface IGame {
    void joinPlayers(IEnumerable<IPlayer> players); //ゲームの参加者。
    void followRule(IRule rule); //ゲームのルール。
    void start(); //ゲームを開始する。参加者とプレイヤーは登録しておくこと。
}

//参加者。
public interface IPlayer {
    void prepareCause(); //勝負要素を準備する。
    ICause serveCause(); //勝負要素を出す。prepareするまでは同じ要素が出される。
    void result(IResult result); //勝負結果。
    IResult result(); //勝負結果。
}

//ルール。
public interface IRule {
    void valid(ICause cause); //勝負要素が認められるものであるか。NGなら例外を投げる。
    IResult match(ICause one, IEnumerable<ICause> others); //oneの勝負結果を示す。
}

//勝負要素(勝負結果の原因)。
public interface ICause { }
//勝負結果。
public interface IResult { }

//勝負要素を出す戦略。
public interface ICauseStrategy {
    void prepare();
    ICause serve();
}

/////////////////////////////クラス/////////////////////////////

//一度だけ行われるゲーム。
//流れはprepare→serve→match→result。
public class OnceGame : IGame {
    private IEnumerable<IPlayer> _players = null;
    private IRule _rule = null;

    void IGame.joinPlayers(IEnumerable<IPlayer> players) { _players = players; }

    void IGame.followRule(IRule rule) { _rule = rule; }

    void IGame.start() {
        foreach(IPlayer player in _players) {
            player.prepareCause();
            _rule.valid(player.serveCause());
        }
        foreach (IPlayer one in _players) {
            IEnumerable<ICause> others = _players
                                         .Where(other => other != one)
                                         .Select(other => other.serveCause());
            one.result(_rule.match(one.serveCause(), others));
            System.Console.WriteLine(String.Format("{0} {1} {2}", one, one.serveCause(), one.result()));
        }
    }
}

//OnceGameの流れで、Drawのresultがある場合は繰り返す。
public class DrawLoopGame : IGame {
    private IEnumerable<IPlayer> _players = null;
    private OnceGame _once;

    public DrawLoopGame(OnceGame once) {
        _once = once;
    }

    void IGame.joinPlayers(IEnumerable<IPlayer> players) {
        ((IGame)_once).joinPlayers(players);
        _players = players;
    }

    void IGame.followRule(IRule rule) { ((IGame)_once).followRule(rule); }

    void IGame.start() {
        while (true) {
            ((IGame)_once).start();
            if(_players.Any(player => player.result() is Draw)) {
                System.Console.WriteLine("--draw--");
                continue;
            }
            System.Console.WriteLine("--finish--");
            break;
        }
    }
}

//戦略に従うだけの参加者。
public class CauseStrategiedPlayer : IPlayer {
    private ICauseStrategy _strategy;
    private string _name;
    private IResult _result = new None();
    private ICause _served = null;

    public CauseStrategiedPlayer(ICauseStrategy strategy, string name) {
        _strategy = strategy;
        _name = name;
    }

    IResult IPlayer.result() { return _result; }
    void IPlayer.result(IResult result) { _result = result; }

    void IPlayer.prepareCause() {
        _served = null;
        _strategy.prepare();
    }

    ICause IPlayer.serveCause() {
        if ( _served == null) {
            _served = _strategy.serve();
        }
        return _served;
    }

    public override string ToString() { return _name; }
}

//ランダムにジャンケンの勝負要素(手)を出す戦略。
public class JankenCauseAtRandom : ICauseStrategy {

    private static int _seed = 0;
    private Random _random;

    private List<ICause> _causes = new List<ICause>() {
        new Guu(), new Choki(), new Paa()
    };

    public JankenCauseAtRandom() {
        //同時に複数生成した場合に、乱数シードが同じになってしまうのを避けるため
        _seed += Environment.TickCount;
        _random = new Random(Environment.TickCount + _seed);
    }

    void ICauseStrategy.prepare() { }
    ICause ICauseStrategy.serve() {
        return _causes[_random.Next(0, _causes.Count())];
    }
}

//標準入力でジャンケンの勝負要素(手)を決める戦略。
public class JankenCauseStdin : ICauseStrategy {
    void ICauseStrategy.prepare() { }

    ICause ICauseStrategy.serve() {
        while (true) {
            Console.Write("グー[g] チョキ[c] パー[p]のどれかのキーを押して[enter]して下さい >");
            string input = Console.ReadLine();
            if (input.Equals("g")) { return new Guu(); }
            if (input.Equals("c")) { return new Choki(); }
            if (input.Equals("p")) { return new Paa(); }
        }
    }
}

//ジャンケンのルール。
public class JankenRule : IRule {
    private Dictionary<Type, Type> _win_pattern = new Dictionary<Type, Type>() {
        { typeof(Guu), typeof(Choki) }
        ,{ typeof(Choki), typeof(Paa) }
        ,{ typeof(Paa), typeof(Guu) }
    };

    private Dictionary<Type, Type> _lose_pattern = new Dictionary<Type, Type>() {
        { typeof(Guu), typeof(Paa) }
        ,{ typeof(Choki), typeof(Guu) }
        ,{ typeof(Paa), typeof(Choki) }
    };

    void IRule.valid(ICause cause) {
        if (cause is Guu || cause is Choki || cause is Paa) {
            return;
        }
        throw new Exception(String.Format("{0}は認められない。", cause));
    }

    IResult IRule.match(ICause one, IEnumerable<ICause> others) {
        Type one_type = one.GetType();
        bool win = others.Any(other => _win_pattern[one_type].Equals(other.GetType()));
        bool lose = others.Any(other => _lose_pattern[one_type].Equals(other.GetType()));
        bool draw = others.Any(other => one_type.Equals(other.GetType()));
        if (win && lose) { return new Draw(); }
        if (win && draw) { return new Win(); }
        if (lose && draw) { return new Lose(); }
        if (win) { return new Win(); }
        if (lose) { return new Lose(); }
        return new Draw();
    }
}

public class Guu : ICause {
    public override string ToString() { return "グー"; }
}
public class Choki : ICause {
    public override string ToString() { return "チョキ"; }
}
public class Paa : ICause {
    public override string ToString() { return "パー"; }
}

public class Win : IResult {
    public override string ToString() { return "勝ち"; }
}
public class Lose : IResult {
    public override string ToString() { return "負け"; }
}
public class Draw : IResult {
    public override string ToString() { return "引き分け"; }
}
public class None : IResult {
    public override string ToString() { return "結果なし"; }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

適当に書いてそれで動くようにクラスを設計します。
見通しよく、使いやすいのが良いクラスなので・・・

例えばコンソールで作るとして、こんなゲームをイメージします。

Let's Play
名前は?
foo

どれを出す?
1:グー 2:チョキ 3:パー 0:終了
1

fooさん: グー 相手: チョキ
fooさんの勝ち

スコア fooさんの勝ち: 1 回 引き分け: 0 回 相手の勝ち: 0 回

どれを出す?
1:グー 2:チョキ 3:パー 0:終了
0

Game End
スコア fooさんの勝ち: 1 回 引き分け: 0 回 相手の勝ち: 0

テキトーにメインを書きます。

var p1 = new Player(GetName());
var p2 = new RobotPlayer();

var score = new Score(p1, p2);
Console.log("Let's Play");

var result = score.Play(p1, p2);
while(result.AtEnd()){
   ShowResult(result);
   ShowScore(score);   

   result = score.Play();
}
Console.log("Game End");
ShowScore(score);

これに合うクラスであればとりあえずオッケー。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/12 23:50

    大きいプログラムについて言及していませんでしたね。大きいプログラムでもおんなじです。
    「ユーザーはこう使いたいよなー」「こうゆうオブジェクトの呼び出ししたいよなー」「ここはこう書いたほうが読みやすいよなー」といった。やりたいこと順に決めていきます。

    責任の分担をして、上位のオブジェクトは下位のオブジェクトについて無関心であるのが流儀だと思います。したがって、上位のオブジェクトから設計するのが基本よい設計につながります。

    キャンセル

0

まず、大きめの紙かホワイトボードに、作りたいものを説明するときに登場する思いつく限りの単語を書き出します。(これれらをビジネスドメイン用語という)
このうち、何かの名前であるものはすべてオブジェクトの候補です。
オブジェクト名をよく眺めると似たもの同士で集めたグループができます。
そのグループを表す名前があれば、それはクラスの候補です。
グループのメンバーにさらに役割や動作の異なる種類がりそれに名前があれば、それはサブクラスの候補です。
グループのメンバーが状態や種別を表すのなら、クラスの代わりに列挙(enum)できるかもしれません。
クラスの集まり眺めるとまた性質や扱いに似た者同士が集まります。
そのクラスの集まりに名前が付けられたら、○○的なものという抽象クラス(あるいはインターフェース)の候補です。
単語のうち、何らかの動詞であるものはオブジェクトの機能でありメソッドの候補です。
メソッド名をよく眺めると関連するもの同士を集めたグループができます。
そのグループを表す名前があれば、○○するものというインターフェースの候補です。
余った単語のうち、あるオブジェクトの一部や状態のことだったら、それはプロパティの候補です。
この時点でプロパティは出し切れません。
同じクラスのオブジェクトの集まりを管理するオブジェクトが必要になるでしょう。
そのためのクラスを定義してもいいですが、とりあえずありもののコレクションライブラリでいいでしょう。
最後にこれらのクラスを作りたかったビジネスロジックを実装するアプリケーションクラスを作ります。
しかしこれはなんとかというアンチパターンで実装後ほどなく破たんします。
そうならないようもう一度書き出して、各単語間の関係を線や丸囲みで整理します。
その線や丸がなるべく階層性を持つように整理します。
依存性の矢印がなるべく重なったりループにならないよう整理します。
クラスを分けたりくっつけたり移動したり消したりインターフェースを切ったりつぶしたりします。
よく見ると、結合度によって境界と島ができます。
この島も階層性を表すように整理します。
この島と島を結ぶ線をなるべく少なくなるように整理します。
依存性が逆流している線をなるべく少なくなるように整理します。
島と島を突き抜けた矢印がなるべく少なくなるように整理します。
この整理で悩んだところが、デザインパターンの候補です。
この島の構成をなぜこうしたのか説明できる言葉を持つことが、人にわかってもらう気もためのキモです。
島のレイヤーが増えて境界に晒すインターフェースをたくさんきるでしょう。
オブジェクト間の関連もまたオブジェクトであることに気づきクラスとインターフェースを切るでしょう。
複雑なオブジェクトの生成方法に悩むでしょう。
オブジェクトの責務が上に行ったり下に行ったりするでしょう。
ビジネスドメイン以外のクラスも必要ですが、これらはとりあえずありもののライブラリやフレームワークを試します。
何日か経ちます。
ファイルやDBといった外部リソースはありもののライブラリやフレームワークを試します。
何日か経ちます
各クラスのテストクラスもそろそろ用意します。
設定クラス、定数クラス、例外クラス、ロガーなど、今は使わなくても追加しておきます。
アプリケーションコンテナの規約にそった実装も必要になります。
これは何時でもすぐにスタートアップできるテンプレートを用意しておきまましょう。
実装を開始します。
うかつな設計に気づきます。
あるオブジェクトがある特殊な状態を表現できないことに気づきます。
あるオブジェクトを取り出すのが異常に面倒くさいことに気づきます。
あるオブジェクトが多対多でなければならなかったことに気づきます。
認証やセキュリティ対策を後から入れるのは大変なことに気づきます。
ライブラリやフレームワークの選択に間違えたことに気づきます。
設計破りの穴をあける決断を迫られミジメな気持ちになります。
自分の愚かさとうぬぼれに気づきのたうちまわります。
以下略・・・

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 88.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る