説明:
複数のレベルを含む駐車場があり、各レベルに複数の列があり、各列に複数のスポットが含まれているとします。質問は、車両を駐車できるかどうかを確認するのに十分な場所があるかどうかを判断することです。
十分なスポットがあるかどうかを決定するのはどのオブジェクトですか?つまり、最初のクエリを受け取ることになっている駐車場オブジェクトですが、最終的には、実際のアルゴリズムを実装するためにレベルまたは行のいずれかである必要があります。
それは単一のクラスではなく、システム全体です。
要件:
駐車場には複数のレベルがあります。各レベルには、スポットの複数の行があります。
駐車場には、オートバイ、車、バスを駐車できます。
駐車場には、バイクのスポット、コンパクトなスポット、大きなスポットがあります。
バイクはどこにでも駐車できます。
車は1つのコンパクトスポットまたは1つの大きなスポットに駐車できます。
バスは、連続して同じ列内にある5つの大きなスポットに駐車できます。小さな場所には駐車できません。
問題は、与えられたシステムが次のようなクエリに答えることができるはずだということです。
車が与えられた場合、システムは車のスポットがあるかどうかを判断できるはずです。そうであれば、答えは次のようになります。
There is a spot on level x row y and spot z
このような問題を解決する際に直面する問題は、どこから始めて、ドメインを正しく表すべき正しいオブジェクトを見つける方法です。
私はどこかから始めようとしましたが、以下が私のコードです:
Enum Size {
SMALL,
COMPACT,
LARGE
}
private class Spot {
private boolean empty;
private int number;
private Size size;
Spot(Size size, int number) {
this.size = size;
}
full() {
this.empty = false;
}
empty() {
this.empty = true;
}
status() {
return this.empty;
}
boolean canPark(Vehicle vehicle) {
return false;
}
}
class LargeSpot extends Spot {
boolean canPark(Vehicle vehicle) {
return
vehicle.size() == Size.LARGE ||
vehicle.size() == Size.COMPACT ||
vehicle.size() == Size.SMALL;
}
}
class CompactSpot extends Spot {
boolean canPark(Vehicle vehicle) {
return
vehicle.size() == Size.COMPACT ||
vehicle.size() == Size.SMALL;
}
}
class SmallSpot extends Spot {
boolean canPark(Vehicle vehicle) {
return vehicle.size() == Size.SMALL;
}
}
private class Row {
private ArrayList<Spot> spots;
Row(List<Spot> spots) {
this.spots = spots;
}
void spot(Spot spot) {
this.spots.add(spot);
}
List<Spot> getSpots(Vehicle vehicle) {
List<Spot> emptySpots = new ArrayList<>();
if (vehicle.size() == Size.LARGE) {
// five consecutive large spots
int length = spots.size();
for (int i = 0; i < length; i++) {
int j = 0;
for (; j < 5; j++) {
if (!spot.canPark(vehicle)) break; // break out of inner loop
}
if (j == 5) { // found five spots
for (int k = i; k < i + j; k++) {
emptySpots.add(spot);
}
i += j;
}
}
} else {
for (Spot spot : spots) {
if (spot.canPark(vehicle)) {
emptySpots.add(spot);
}
}
}
return emptySpots;
}
}
private class Level {
private final int number;
private final ArrayList<Row> rows;
Level(number, rows) {
this.number = number;
this.rows = rows;
}
}
class ParkingLot {
private List<Level> levels;
ParkingLot(List<Level> levels) {
this.levels = levels;
}
public String status(Vehicle vehicle) {
List<Spot> emptySpots = new ArrayList<>();
for (Level level : levels) {
for (Row : level.rows()) {
if (row.canPark(vehicle)) {
emptySpots.addAll(row.getSpots(vehicle));
}
}
}
return emptySpots;
}
}
public class Main {
private final List<Level> floors = new ArrayList<>();
floors.add(
new Level(
0,
new Row[4]{new SmallSpotSpot(), new CarSpot(), new CarSpot()}));
floors.add(
new Level(
1,
new Row[4]{
new LargeSpot(), new LargeSpot(), new LargeSpot(), new LargeSpot()}));
private final Lot lot = new ParkingLot(floors);
private final Car car = new Car(Size.Compact);
System.out.println(lot.status(car));
}
コードは間違いなく多くの場所で壊れていますが、読者は私が達成しようとしていることをぼんやりと理解できます。また、大型車両の駐車は、優れたソリューションを設計する際の問題点の1つです。
何をモデル化するかが問題です。
一方で、現実の状況には明らかな構造があります。たくさんあり、レベルがあり、行があり、最終的にはスポットがあります。したがって、1つのアプローチは、これらのアイテムのすべてをエンティティとしてモデル化し、それらの多くをコレクションとしてモデル化することです。オブジェクトの階層内。
ただし、アプリケーションの作成では、必ずしも現実世界にあるすべてのものをモデル化する必要はありません。モデル化するものはすべて、一流のエンティティである必要はありません。
私が得ているのは、ロット、レベル、行、スポット、そして属性のタイプ/サイズ(コンパクト、ハンディキャップ、ボート/ rvなど)、無料の主キーを持つ単一のテーブルですべてのスポットをモデル化できることです。 /占有。
利用可能なスポットの検索はテーブルに対するクエリであり、スポットを空き/占有としてマークする更新は単純なトランザクションです。
他にも多くのオプションがあります。OOPでは、占有と非占有の2つのシンプルなリストを維持できます。占有されていないリストは、カテゴリごとに複数のリストに分割することも、並べ替えることもできます。スポットのコレクションを保持しているマネージャーは、リスト間でアイテムを検索および移動できます。
要約すると、domainクエリとコマンドを実際の駐車場の明らかな実際の構造を超えて研究する必要があります。この余分な構造(レベル、行など)は、フリースポットがどこにあるかを単に特定する以外、実際のドメイン値をほとんど提供しない可能性があります。したがって、複数のテーブルに編成する理由はほとんどありません。また、レベルを単純な(値)属性としてキャプチャできる場合、「レベル」自体をエンティティとしてモデル化する理由はおそらくないでしょう。
(数万のスポットの駐車場が現代のコンピューターシステムに負担をかけることは決してないので、ほとんどすべてのアプローチが機能します。そのような場合、@ DocBrownが示唆しているように、最も簡単な方法を実行します。インターネットの規模に達する可能性があり、最終的には対処する必要のあるスケーリングが必要になります。)
私たちはすべてをモデル化することはできません。したがって、モデリングは本質的に抽象であり、無関係な詳細を隠すという意味で、真に関連するものに焦点を当てることができます。
ドメインに関連するユースケースを特定します。それらから、モデルを変更するためのクエリとコマンドを特定します(入力/与えられたものと出力/望ましい結果に注目することから始めます)。そして、それらから、実際のクエリとコマンドとともにモデル化するものを特定します。このレベルの理解により、どのモデル化されたエンティティにどのような責任があり、どれが単なる価値属性であるかがわかります。必要以上にモデル化すると、明瞭さが失われる可能性があります。
更新のための更新:必要以上にモデル化しないでください。値属性が行う場合はサブクラスを使用しないでください。属性が行う場合はオブジェクト階層を使用しないでください。
たとえば、サブクラスを使用してスポットのサイズをモデル化することを示しています。特定のスポットが障害者のスポットである場合はどうなりますか?次に、ハンディキャップスポットサイズごとにサブクラスが必要です(例:クラスRVHandicappedSpot)。これはクラスの爆発であり、属性を使用してモデル化できるものをモデル化するためにクラスを使用すると、コードのにおいがします。
さらに、属性を使用して、列挙型だけでなく数値範囲を持つことができます。スポットの長さ。クラスを使用してモデル化する場合、(数値と比較して)enumと同等のより明確な値に制限されます。
複数の場所でのバス駐車について:
基本的に、2つの別々の問題があります。バスの駐車場を特定することと、それらを割り当てることです。これらは異なる時間に実行できます。一緒に行う必要はありません。
すべての潜在的なバス駐車スポットを事前にモデル化することをお勧めします、それ自体は他のスポットのコレクションです。次に、どのバスパーキングスポットが完全に空いているかを判断し、必要に応じてそれらを配ることができます。バス駐車場に属するランダムな駐車場を配ることによって不注意にバス駐車を妨げないようにするなど、ここではいくつかの考慮事項があります。
バスパーキングの操作は、複雑なデータ構造によって解決されるものではなく、ほとんどがアルゴリズムによるものだと思います。これは、プロセッサにレジスタペアがある場合など、コンパイラテクノロジでのレジスタ割り当て(グラフの色分けなど)に似ています。
スポットと車両が一致するかどうかを確認するには、おそらく3つのオプションがあります。スポットは、車両が適合するかどうかを確認できます。車両は、スポットが適合するかどうかを確認できます。または、マネージャ(ロットまたは第三者)が確認できます。試合のために。
ドメインに最適なのは、質問するのに適した質問です。
トレードオフの基準には、次のものが含まれます。スポットまたはビークルのいずれかに、質問を委任する必要がある専門化(サブクラス)がありますか。もしそうなら、彼らは相談されるべきです。スポットまたは車両のいずれかに、他の人には見えないマッチングに影響を与えるプライベートデータがありますか?もしそうなら、彼らは試合を承認/拒否する機会を与えられるべきです。
バスの駐車スポットを、スポットのコレクションであるスポットサブクラスにすることができます。その場合、スポットの種類間にいくつかのクラス階層がありますが、(スポットサイズに基づく)汎用スポットアルゴリズムが十分であるか、またはサブクラスに特殊なオーバーライドが必要かどうかはわかりません。
また、外部のクライアントが使用するための安定したインターフェースが必要です。
したがって、私が最初に取るのは、それがマネージャーの(ロットの)責任であり、コードが内部的に進化する場合でも、外部クライアントの観点からは、消費するクライアントコードはマネージャーのみを処理すればよいということです。
ただし、マネージャーは、スポットまたは車両または両方に委任することを単に選択する場合があります(たとえば、両方が「はい」と答える必要がある場合、それは一致です)。
class ParkingLot {
boolean Matches(Spot spot, Vehicle vehicle) {
return spot.Matches(vehicle) && vehicle.Matches(spot);
}
}
最初に要件を確認します
駐車場に入り、空いている場所を見つけるために車で移動する必要がある場合、その責任はその場でも、列でも、駐車場でも、ドライバーの責任ではありません。多くの責任は、空いている場所を数えることだけであり、残っている場所があれば入場できるようにすることです。
しかし、あなたのケースははるかにインテリジェントなアプローチを示唆しています:
したがって、明らかに、責任は複数のオブジェクト間で共有されます。
素朴なアプローチによるモデル
あなたの駐車場は制約されたcompositeのように見えます:駐車スポットは葉、列、そして駐車場は葉の複合材料。
一見すると、主な責任はコンポジットにあり、最初の一致する要素が見つかるまでコンポジットをチェーンに渡します。一種の再帰的な責任...
ただし、より高いレベルの最適化、つまり、最も一致する場所を選択するより高いレベルの要素があること(たとえば、オートバイが到着した場合、無料のオートバイスポットを優先し、コンパクトな場所を想定して空車がない場合のみ)リーフ(つまり、それが空いているかどうかを伝える)、行(つまり、最も近い一致するフリースポットを伝える)、および駐車場(つまり、隣接する空き領域を維持するために最も多くの車がある行を選択した)の間で責任を分割します。バス用)。
より良いアプローチのモデル
最良の場所を選択する責任は、実際には駐車場の責任ではなく、割り当て機関の責任です。それにもかかわらず、車と駐車場の間の相互作用は、駐車場に部分的な責任を与える必要があります:
Car ---<request>---> Parking lot ---<request>---> Allocator
<---<answer>---- <---<choice>----
駐車場が一種の複合物である場合、アロケータはvisitorとして実装できます。コンポジット、およびいくつかの集約を構築します(たとえば、潜在的な空きスポットの順序付きリスト)。コンポジットは訪問者と協力する必要があります。
それをさらに柔軟で強力にするために、訪問者は戦略を使用して、潜在的な空きスペースを順序付けすることができます車両の特定のサイズ、および異なる種類の割り当てヒューリスティックスの実装。
これは過剰設計ですか?現実世界へのリンク
私の地域にはいくつかの空港があり、各スポットの検出器は、撮影された場合は赤い線を表示し、空いている場合は緑のライトを表示します(スポットの状態では葉のレベルにあります)。各列の前には、列にあるフリースポットの数を示す標識があり、入り口には、ロット内のフリースポットの数があります。ここでは割り当ての責任はありませんが、明らかに、識別された各オブジェクトが何らかの形で全体の責任に関与します。
ですから、いや、これはスマートパーキングとスマートシティの時代では過剰設計されているとは思いません;-)
単純なシステムの場合、あなたのシステムと同じように、責任を判断する最も簡単な方法は、迅速なユースケースを作成することです。それから、シーケンス図を描きます。自分が取り組んでいる抽象化の適切なレベルでユースケースを作成する方法を学ぶと、かなり簡単になる傾向があります。
ユースケース:駐車スポットを探す
1-オペレーターがシステムから車両の駐車場を見つけることを要求します。
2-システムは、駐車場が車両が収まる駐車スポットを見つけるように要求します。
3-駐車場は駐車場をオペレーターに返します。
4-オペレーターが駐車スポットの場所を報告します。
これから、「オペレーター」サブシステム(UI)があると推測できます。 ParkingLotおよびParkingSpotサブシステム。システムサブシステム(通常、ユースケース処理ステップをシーケンス処理するサブシステムのみ。私はそれらをオペレーションクラスと呼ぶ傾向があります)。 「車両」は、サブシステムまたは「FindParkingSpot」操作の単なるパラメーターとしてモデル化できます。時には、一方のアプローチが他方よりもうまく機能することがあります。
通常、対応するシーケンス図を描くと、責任が明確になります。この場合、ParkingLotは、ParkingSpotを検索して駐車場所を決定する方法を知っています。
ParkingLotがどのように決定を下すかは、設計の次のレベルに完全に依存します。「どのオブジェクトが責任があるか」にこだわることは、設計者が実用的に考えている「良い」という漠然としたアイデアを満たすために煮詰める傾向があるため、通常は無意味です。一般にかなり恣意的です。
あなたはまだOOP学習プロセスにいるようですので、識別されたサブシステムを直接クラスに変換する以外に何も心配する必要はありません。次に進むと、他の問題が発生する可能性がありますこれらのサブシステムがいくつかのクラスに変わる可能性があるかどうかを心配します。ただし、必要な経験がなければ、より高度な概念を適用しようとすると、ほとんどの場合、単純な設計よりもはるかに悪い設計になるため、心配する必要はありません。サブシステムを直接クラスに変換します。サブシステムへの責任の割り当てをうまく行うだけで、非常に使いやすい設計になります。
また、行とレベルは駐車場の属性にすぎないため、責任はありません。