🙈
オブジェクト指向エクササイズを取り入れてみる
概要
オブジェクト指向エクササイズの9つのルールというのがあるが全てをきちっとやろうとするとだいぶ大変
なのでその中でも導入しやすいものを取り入れてみる
- 1つのメソッドにつきインデントは1段階までにすること
- else句を使用しないこと
- すべてのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
- 名前を省略しないこと
- すべてのエンティティを小さくすること
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- Getter、Setter、プロパティを使用しないこと
今回は太字の項目を意識してみる
実際にコードを書いてみる
言語:Java 11(Spring Boot 2.3.3)
Lombokも使用
@Accessors(fluent = true)
@RequiredArgsConstructor(staticName = "create")
public class Item {
@Getter
@NonNull
final String name;
@NonNull
final ItemCategory category;
@NonNull
final Price price;
@NonNull
final Quantity quantity;
public String category() {
return category.text();
}
public int price() {
return price.value();
}
public int quantity() {
return quantity.value();
}
}
ItemCategoryやPrice、Quantityは値オブジェクトとしてラッピングをしている
ItemモデルとしてはItemCategoryやQuantityを直接返すのではなくItemモデルとして使いたいカテゴリーの名称や商品の個数を直接返すようにする
nameに関してはlombokの@Getter
を利用
@Accessors(fluent = true)
を設定しているので、getのprefixはつかない
インスタンス変数をnull許容にする
nullを返す場合にはOptional型で返すようにした方が利用者にnullが返る可能性を意識させられるがモデルの場合にそうしてしまうと扱いづらくなる
そういった場合にはInteger型やBoolean型などで返すようにする
@Accessors(fluent = true)
@RequiredArgsConstructor(staticName = "create")
public class Item {
@Getter
@NonNull
final String name;
@NonNull
final ItemCategory category;
final Price price;
@NonNull
final Quantity quantity;
public String category() {
return category.text();
}
public Integer price() {
if (price == null) {
return null;
}
return price.value();
}
public Optional<Integer> priceOptional() {
if (price == null) {
return Optional.empty();
}
return Optional.ofNullable(price.value());
}
public int quantity() {
return quantity.value();
}
}
使用してみるとこんな感じに
Item item = Item.create("サンプル", ItemCategory.ゲーム, null, Quantity.ZERO);
System.out.println(item.price());
item.priceOptional().ifPresent(System.out::println);
価格を出力したいだけなのに使いづらい…
今回はInteger型でnullを返しているがnullを返さないといけない場合は設計がおかしくないかを見た方が良さそう
Itemモデルの保持しているQuantityを増やしたい
@Accessors(fluent = true)
@RequiredArgsConstructor(staticName = "create")
public class Item {
@Getter
@NonNull
final String name;
@NonNull
final ItemCategory category;
@NonNull
final Price price;
@NonNull
final Quantity quantity;
public String category() {
return category.text();
}
public int price() {
return price.value();
}
public int quantity() {
return quantity.value();
}
public Item addQuantity(Quantity quantity) {
return new Item(name, category, price, quantity.add(quantity));
}
}
イミュータブルなオブジェクトなので加算する度にオブジェクトの再生成を行っているがやりすぎると処理のコストが大きくなるのでどこまで厳密にやるかは要検討
値オブジェクトを直接取り出したい
値オブジェクトを直接取り出して使用したい場合はextractXXXなどの抽出用メソッドを用意する
識別子関連以外ではあまり必要になる機会もないと思う
public Price extractPrice() {
return price;
}
今回作成したその他のクラス
@Accessors(fluent = true)
@Getter
@RequiredArgsConstructor
public enum ItemCategory {
ゲーム(0, "GAME");
final int value;
final String text;
}
@Accessors(fluent = true)
@Getter
public class Price {
static final String ERROR_MESSAGE = "price should be between %d and %d.";
static final int MIN = 1;
static final int MAX = 99999;
final int value;
Price(int value) {
if (value < MIN || value > MAX) {
throw new IllegalArgumentException(String.format(ERROR_MESSAGE, MIN, MAX));
}
this.value = value;
}
public static Price of(int value) {
return new Price(value);
}
}
@Accessors(fluent = true)
@Getter
public class Quantity {
public static Quantity ZERO = new Quantity(0);
static final String ERROR_MESSAGE = "quantity should be between %d and %d.";
static final int MIN = 0;
static final int MAX = 999;
final int value;
Quantity(int value) {
if (value < MIN || value > MAX) {
throw new IllegalArgumentException(String.format(ERROR_MESSAGE, MIN, MAX));
}
this.value = value;
}
public static Quantity of(int value) {
return new Quantity(value);
}
public Quantity add(Quantity quantity) {
return new Quantity(value + quantity.value);
}
}
Discussion