はじめに
今回は、オブジェクト指向の継承を自己解釈してみます。
例として、次のようなAnimalクラスからまずみていきます。
筆者は主に業務系アプリケーションの開発現場で作業しており、その際にオブジェクト指向をかじっていくことになったわけですが、調べたりした際にAnimalクラスなるものが例として挙げられているのを見ました。
しかし、これは実務的にAnimalクラスなんてものは当然出てこず、どう扱ったらいいんだ? となりよくわからなかったので、
今回はこれが業務アプリケーション的に例えばどういったものに該当するのか? ということを考えていきたいと思います。
また、コードはC#.NETおよび、WPFを使って作成していきますが、おまじない部分以外は他言語で開発経験があればわかる程度の簡単なコードで作成しているつもりです。
Animalクラス?
public class Animal
{
public Animal(string name)
{
this.Name = name;
}
public string Name { get; set; }
public string Voice { get; set; }
}
public class Dog : Animal
{
public Dog(string name) : base(name)
{
Voice = "ワン";
}
}
public class Cat : Animal
{
public Cat(string name) : base(name)
{
Voice = "にゃあ";
}
}
static void Main(){
Animal dog = new Dog("コタロウ"); //インスタンスを生成
Animal cat = new Cat("タマ"); //インスタンスを生成
AnimalVoice(dog);
AnimalVoice(cat);
void AnimalVoice(Animal animal){
Console.WriteLine(animal.Name + "は" + animal.Voice + "と鳴く");
}
}
実行結果
コタロウはワンと鳴く
タマはにゃあと鳴く
雰囲気としてはこんな感じのものです。
コードの細かい点については今回は本題ではないのでスキップ。
上例にあるAnimalクラスは、オブジェクト指向における「基底クラス」というものになります。
いわゆる継承元と呼ばれるクラスで、今回であればDogクラスやCatが「派生クラス」となります。
Animalクラスは、「名前と鳴き声を持っている」が今回のクラス定義としています。
つまるところ、何かの概念(Animal=動物)から共通的な部分を抜き出し、それを派生クラスが再利用できるといったものであるのだろうという形でしょうか。
そこで、基底クラスはオブジェクトの「概念」、派生クラスは「実体」として解釈していきます。
(Animalだと名前を持っているとは限らないので、Petクラスとかにした方が良いかもしれませんね)
業務アプリケーションに変換する
はじめに記載したように、筆者は主に業務アプリケーション系の開発がメインでした。
その中で、当然といえば当然なのですがAnimalクラスそのものが出現することはほぼなく、
それではどういったものがそれに該当するのか?ということを考えていくことになります。
当然、ロジック上であれば様々なものがあげられると思うのですが、
今回は自己解釈がテーマであるので、純粋なクラス継承ではなく、
業務アプリケーション上でよくでてくるUIコントロールを「実体」の先として考えていきたいと思います。
<Window x:Class="WpfApp1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="150">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<StackPanel Height="300">
<Label>動物</Label>
<ComboBox ItemsSource="{Binding Item1}"
SelectedValue="{Binding Value1}"
Width="80"
HorizontalAlignment="Left"/>
<Label>植物</Label>
<ListBox ItemsSource="{Binding Item2}"
SelectedValue="{Binding Value2}"
Width="80"
HorizontalAlignment="Left"/>
<Button Command="{Binding ExceuteCommand}"
Width="100"
HorizontalAlignment="Left">実行</Button>
<TextBlock />
<TextBlock Text="{Binding Result}"/>
</StackPanel>
</Grid>
</Window>
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
namespace WpfApp1.ViewModel
{
class MainWindowViewModel : ObservableObject
{
private List<string> _item1;
/// <summary>
/// 選択肢
/// </summary>
public List<string> Item1
{
get { return _item1; }
set
{
SetProperty(ref _item1, value);
}
}
private List<string> _item2;
/// <summary>
/// 選択肢
/// </summary>
public List<string> Item2
{
get { return _item2; }
set
{
SetProperty(ref _item2, value);
}
}
private string _value1;
/// <summary>
/// 入力値1
/// </summary>
public string Value1
{
get => _value1;
set
{
SetProperty(ref _value1, value);
// 実行可否を更新
ExceuteCommand?.NotifyCanExecuteChanged();
}
}
private string _value2;
/// <summary>
/// 入力値2
/// </summary>
public string Value2
{
get => _value2;
set
{
SetProperty(ref _value2, value);
// 実行可否を更新
ExceuteCommand?.NotifyCanExecuteChanged();
}
}
private string _result;
/// <summary>
/// 結果
/// </summary>
public string Result
{
get => _result;
set
{
SetProperty(ref _result, value);
}
}
/// <summary>
/// 実行コマンド
/// </summary>
public IRelayCommand ExceuteCommand { get; private set; }
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindowViewModel()
{
Item1 = new List<string>() {
"犬","猫","鳥"
};
Item2 = new List<string>() {
"花","木","草"
};
ExceuteCommand = new RelayCommand(
execute: () =>
{
Result = ("動物:" + Value1 + "\r\n植物:" + Value2);
},
canExecute: () =>
{
return !string.IsNullOrEmpty(Value1) && !string.IsNullOrEmpty(Value2);
});
}
}
}
実行結果
実際の業務画面はもっと複雑ですが、ひとまずコンボボックスとリストボックスで選択された値を表示するアプリができました。
今回はこの2つをベースに、概念を抽出して基底クラス相当のものを作成します。
ItemSelectorクラス
この2つに共通する概念は、View側にあるように
ItemSourceとSeletedItemがあるコントロールになります。
つまり、リストの値と選択された値の2つです。
そのため、これらを持つクラスが必要そうです。
この2つをプロパティとして持つItemSelectorクラスを作成してみます。
public class ItemSelector: ObservableObject
{
public ItemSelector(List<string> items)
{
_items = items;
}
private List<string> _items;
/// <summary>
/// 選択肢
/// </summary>
public List<string> Items
{
get { return _items; }
set
{
SetProperty(ref _items, value);
}
}
private string _selectedItem;
/// <summary>
/// 選択肢
/// </summary>
public string SelectedItem
{
get => _selectedItem;
set
{
SetProperty(ref _selectedItem, value);
}
}
}
新しく作った基底クラス相当のItemSelectorクラスをベースに、コードをリファクタリングしていきます。
<Window x:Class="WpfApp1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="150">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<StackPanel Height="300">
<Label>動物</Label>
<ComboBox ItemsSource="{Binding Item1.Items}"
SelectedValue="{Binding Item1.SelectedItem}"
Width="80"
HorizontalAlignment="Left"/>
<Label>植物</Label>
<ListBox ItemsSource="{Binding Item2.Items}"
SelectedValue="{Binding Item2.SelectedItem}"
Width="80"
HorizontalAlignment="Left"/>
<Button Command="{Binding ExceuteCommand}"
Width="100"
HorizontalAlignment="Left">実行</Button>
<TextBlock />
<TextBlock Text="{Binding Result}"/>
</StackPanel>
</Grid>
</Window>
class MainWindowViewModel : ObservableObject
{
private ItemSelector _item1;
public ItemSelector Item1
{
get => _item1;
set
{
SetProperty(ref _item1, value);
}
}
private ItemSelector _item2;
public ItemSelector Item2
{
get => _item2;
set
{
SetProperty(ref _item2, value);
}
}
private string _result;
/// <summary>
/// 結果
/// </summary>
public string Result
{
get => _result;
set
{
SetProperty(ref _result, value);
}
}
/// <summary>
/// 実行コマンド
/// </summary>
public IRelayCommand ExceuteCommand { get; private set; }
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindowViewModel()
{
Item1 = new ItemSelector(new List<string>() { "犬", "猫", "鳥" });
Item1.PropertyChanged += (sender, e) =>
{
ExceuteCommand?.NotifyCanExecuteChanged();
};
Item2 = new ItemSelector(new List<string>() { "花", "木", "草" });
Item2.PropertyChanged += (sender, e) =>
{
ExceuteCommand?.NotifyCanExecuteChanged();
};
ExceuteCommand = new RelayCommand(
execute: () =>
{
Result = ("動物:" + Item1.SelectedItem + "\r\n植物:" + Item2.SelectedItem);
},
canExecute: () =>
{
return !string.IsNullOrEmpty(Item1?.SelectedItem) && !string.IsNullOrEmpty(Item2?.SelectedItem);
});
}
}
これでUIコントロールであるComboBoxおよびListBoxが
ItemSourceとSeletedItemの概念を持つ、ItemSelectorクラスを具体的な実体として実現できたことになります。
共通部分のプロパティがクラス化されて、結果的にViewModelのクラスのコードが少し減りました。
一般的な例であれば、
Animalクラスに対して、DogクラスやCatクラス
今回の例は、
ItemSelectorクラスに対して、ComboBoxやListBox
こういった形で、層を越えても継承のようなものが存在しそうです。
まとめ
オブジェクト指向の継承は、コードクラス上の継承そのものだけではなく、
今回例のようなコードとUIとの関係性で継承らしきものをつくることもできそうです。
重要なのは、共通する概念を抽出することで、コードで継承を使うことそのものではないのではないかという考え方になります。
今回は標準的なコントロールをベースに考えましたが、
業務アプリケーションでは実業務フローをベースにした複雑な概念を画面や処理で実現していくことになります。
Animalクラスに対する実体(動物)や、今回作ったItemSelectorクラスに対するUIコントロールだけでなく、
業務処理における抽象的な概念をうまく抽出してまとめられると、
コーディングそのものに限らず開発作業が楽になっていくかもしれません。