コードの構造が複雑になる原因のひとつが「密結合」です。密結合はコードの保守性や拡張性を損ない、プロジェクトの成長を阻害します。
この記事では、RPGゲームを題材に、「悪い例」と「改善された例」のサンプルプログラムを使いながら、publicなアクセス修飾子による密結合を解消し、internalを活用して適切な結合度を保つ設計について解説します。
C#のコードを例にしていますが、他のプログラミング言語にも応用可能です。
密結合とは
密結合とは、複数のクラスやモジュールが強く依存し合っている状態を指します。この状態では、ひとつの変更が他のクラスにも影響を及ぼすため、コードの保守や拡張が困難になります。適切な修飾子や分割手法を用いることで、結合度を減らし、柔軟な設計を実現することが重要です。
publicの使いすぎが引き起こす問題
public修飾子はどのクラスやアセンブリからもアクセスが可能なため便利ですが、無闇に使用すると密結合が発生しやすくなります。
特に、内部でしか使用しないメソッドやフィールドをpublicにすることで、他のクラスが直接アクセスできるようになり、依存関係が増えてしまいます。
たとえば、Aクラスのpublicなメソッドが他のクラスから頻繁に呼び出される場合、Aクラスの実装を変更すると、そのメソッドを使っていたすべてのクラスに影響を与えることになり、保守が困難になります。また、予期しない使用や誤った使用も発生しやすくなり、バグが発生するリスクも高まります。
internalの活用で結合度をコントロールする
internal修飾子を使用すると、同一アセンブリ(プロジェクト)内のクラスのみがアクセスでき、外部のアセンブリからのアクセスを制限できます。これにより、クラスやメソッドのアクセス範囲を明確に制御し、結合度を低く保つことが可能です。
たとえば、RPGゲームのクラス設計において、キャラクターのステータスを操作するメソッドやアイテム管理クラスのメソッドをinternalに設定すると、同一アセンブリ内でしかアクセスできなくなり、外部からの直接操作を防げます。
これにより、コードの内部構造を隠しつつ、必要な範囲内での操作が可能となるため、保守性が向上し、外部からの誤操作によるバグも防げます。
internalを活用することで、プロジェクト内で結合度を抑え、柔軟かつ安全なコード設計が実現します。
サンプルコードによる解説
ここでは、RPGゲームのキャラクターとアイテム管理を想定した例を用いて、密結合を解消するための設計改善方法について解説します。特に、publicの使いすぎによる密結合を防ぎ、internalを活用してクラスやメソッドの結合度を抑える方法を示します。
悪い例のサンプルプログラム
以下は、publicを多用して密結合を招いた悪い設計の例です。このコードでは、「Player」クラスが直接「Inventory」クラスにアクセスしており、密結合の典型的なケースとなっています。
public class Player
{
public Inventory Inventory { get; set; } = new Inventory();
public void EquipItem(string itemName)
{
if (Inventory.HasItem(itemName))
{
Console.WriteLine($"{itemName} equipped!");
}
else
{
Console.WriteLine($"{itemName} not found.");
}
}
}
public class Inventory
{
public List<string> Items { get; set; } = new List<string>();
public bool HasItem(string itemName)
{
return Items.Contains(itemName);
}
public void AddItem(string itemName)
{
Items.Add(itemName);
}
}
問題点:
Playerクラスが直接Inventoryクラスに依存しています。Inventoryクラスの変更がPlayerクラスに影響を与える可能性が高く、保守性が低くなります。InventoryのItemsプロパティがpublicになっており、外部から直接操作可能です。このため、Inventoryの内部データ構造が外部からの操作で簡単に破壊されるリスクがあります。
良い例のサンプルプログラム
以下は、悪い設計を改善した例です。ここでは、internalを活用し、クラス同士の結合度を抑えています。これにより、クラスの内部構造を隠しつつ、必要な操作だけを公開しています。
internal class Player
{
private readonly Inventory inventory;
public Player()
{
inventory = new Inventory();
}
public void EquipItem(string itemName)
{
if (inventory.HasItem(itemName))
{
Console.WriteLine($"{itemName} equipped!");
}
else
{
Console.WriteLine($"{itemName} not found.");
}
}
public void AddItemToInventory(string itemName)
{
inventory.AddItem(itemName);
Console.WriteLine($"{itemName} added to inventory.");
}
}
internal class Inventory
{
private List<string> items = new List<string>();
public bool HasItem(string itemName)
{
return items.Contains(itemName);
}
public void AddItem(string itemName)
{
items.Add(itemName);
}
}
改善点:
PlayerクラスとInventoryクラスはinternalとして定義され、同じアセンブリ内での使用に限定されています。Inventoryクラスのitemsリストはprivateに変更され、外部から直接アクセスできなくなりました。AddItemメソッドを通じてのみアイテムの追加が可能です。PlayerクラスにはAddItemToInventoryメソッドが追加され、外部から直接Inventoryを操作せずにアイテム追加が可能です。
この改善により、クラス間の結合度が低くなり、Inventoryの内部構造に変更があってもPlayerクラスに大きな影響を与えずに済むようになります。
まとめ
- 密結合は保守性や拡張性を損なう。
- publicを使いすぎると、結合度が高くなる。
- internal修飾子を使うことで、クラスやメソッドのアクセス範囲を制限できる。
適切な設計は、コードの寿命を延ばし、開発コストを削減するための重要な要素です。密結合を解消し、internal修飾子を活用することで、より柔軟でメンテナンスしやすいコードを実現できます。
