udemy(参考に記載)でC#のCompositePatternを聴講し、普段使っているMatlabのAppDesignerに実装してみた。
仕様
末端のエリアを選択した状態でAlertをクリック→その末端エリアを含む親エリアも警報状態とする。
任意のエリアを選択した状態でReleaseをクリック→子エリアすべての警報を解除する。
末端エリアを選択した状態で警報状態にはできない(一括警報は禁止)。
フォルダ階層
Main GUI
Main.mlapp
properties (Access = private)
areas_ cell
end
methods (Access = private)
% TreeにNodeを追加
function AddNode(app, area, parentNode)
if exist('parentNode','var')
node = uitreenode(parentNode, 'Text', area.Name, 'Icon', 'green.png');
else
node = uitreenode(app.tree_List, 'Text', area.Name, 'Icon', 'green.png');
end
node.NodeData = area;
children = area.GetChildren();
for i_node = 1:numel(children)
app.AddNode(children{i_node}, node);
end
end
% Icon変更の再帰処理
function SetImage(app)
nodes = app.tree_List.Children;
for i_node = 1:numel(nodes)
app.SetImageMethod(nodes(i_node));
end
end
function SetImageMethod(app, node)
area = node.NodeData;
if ~isempty(area)
if area.GetCondition() == Condition.Alert
node.Icon = 'red.png';
else
node.Icon = 'green.png';
end
end
children = node.Children;
for i_child = 1:numel(children)
app.SetImageMethod(children(i_child));
end
end
end
% Callbacks that handle component events
methods (Access = private)
function startupFcn(app)
addpath('Area');
addpath('Data');
movegui(app.UIFigure, 'center');
entities = LayerFake.GetData();
for i_entity = 1:numel(entities)
entity = entities(i_entity);
switch entity{1}.Kind
case 1 % BlockArea
app.areas_{end+1} = BlockArea(entity);
case 2 % MeasureArea
app.areas_{end+1} = MeasureArea(entity);
end
end
ids = cellfun(@(x) x.Id, app.areas_);
for i_area = 1:numel(app.areas_)
area = app.areas_{i_area};
parentIndx = find(ids==area.ParentId, 1);
if ~isempty(parentIndx)
app.areas_{parentIndx}.Add(area); % 親に追加
end
end
parentIds = cellfun(@(x) x.ParentId, app.areas_);
rootIndx = find(parentIds==0);
if isempty(rootIndx)
warndlg('rootがありません');
return
end
for i_root = rootIndx
app.AddNode(app.areas_{i_root});
end
expand(app.tree_List, 'all');
end
% Alertボタンクリック時のコールバック
function pb_AlertButtonPushed(app, event)
area = app.tree_List.SelectedNodes.NodeData;
if isempty(area)
return
end
area.Alert();
app.SetImage();
end
% Releaseボタンクリック時のコールバック
function pb_ReleaseButtonPushed(app, event)
area = app.tree_List.SelectedNodes.NodeData;
if isempty(area)
return
end
area.Release();
app.SetImage();
end
% Close request function: UIFigure
function UIFigureCloseRequest(app, event)
rmpath('Area');
rmpath('Data');
delete(app)
end
end
各Class
Composite
Abstract
AreaBase.m
classdef (Abstract) AreaBase < handle
properties (SetAccess = private, GetAccess = public)
entity_
Id int32
ParentId int32
Name string
end
methods (Access = protected)
function this = AreaBase(kaisouEntity)
this.entity_ = kaisouEntity{1};
this.Id = this.entity_.Id;
this.ParentId = this.entity_.ParentId;
this.Name = this.entity_.Name;
end
end
methods (Abstract = true)
Add
Alert
Release
GetCondition
GetChildren
end
end
実装
子階層を持てるフォルダーのようなクラス
BlockArea.m
classdef BlockArea < AreaBase
properties (SetAccess = private, GetAccess = private)
areas_ cell
end
methods (Access = public, Sealed = true)
function this = BlockArea(kaisouEntity)
this = this@AreaBase(kaisouEntity);
end
function Add(this, areaBase)
this.areas_{end+1} = areaBase;
end
function Alert(this)
warndlg('一括警報設定はできません');
end
function Release(this)
for i_area = 1:numel(this.areas_)
this.areas_{i_area}.Release();
end
end
function out = GetCondition(this)
for i_area = 1:numel(this.areas_)
area = this.areas_{i_area};
if area.GetCondition() == Condition.Alert
out = Condition.Alert;
return
end
end
out = Condition.Normal;
end
function out = GetChildren(this)
out = this.areas_;
end
end
end
子階層を持てないファイルのようなクラス
MeasureArea.m
classdef MeasureArea < AreaBase
properties (SetAccess = private, GetAccess = private)
condition_
end
methods (Access = public, Sealed = true)
function this = MeasureArea(kaisouEntity)
this = this@AreaBase(kaisouEntity);
this.condition_ = Condition.Normal;
end
function Add(this, areaBase)
msg = sprintf('Addはできません this.Id=%d area.Id=%d', this.Id, areaBase.Id);
warndlg(msg);
end
function Alert(this)
this.condition_ = Condition.Alert;
end
function Release(this)
this.condition_ = Condition.Normal;
end
function out = GetCondition(this)
out = this.condition_;
end
function out = GetChildren(this)
out = {};
end
end
end
状態のEnumeration
Condition.m
classdef Condition
enumeration
Normal
Alert
end
end
Data
LayerEntity.m
classdef LayerEntity
properties (SetAccess = private, GetAccess = public)
Id int64
ParentId int64
Kind int8
Name string
end
methods (Access = public)
function this = LayerEntity(id, parentId, kind, name)
this.Id = id;
this.ParentId = parentId;
this.Kind = kind;
this.Name = name;
end
end
end
動作確認のダミーデータ
LayerFake.m
classdef LayerFake
properties (SetAccess = private, GetAccess = private)
end
methods (Access = public, Static = true)
function out = GetData()
out = {};
% =========================
out{end+1} = LayerEntity(1, 0, 1, "北海道");
% =========================
out{end+1} = LayerEntity(2, 0, 1, "東北");
% =========================
out{end+1} = LayerEntity(3, 0, 1, "関東");
out{end+1} = LayerEntity(11, 3, 1, "東京");
out{end+1} = LayerEntity(12, 11, 2, "立川");
out{end+1} = LayerEntity(13, 11, 2, "町田");
out{end+1} = LayerEntity(14, 3, 1, "神奈川");
out{end+1} = LayerEntity(15, 14, 2, "横浜");
% =========================
out{end+1} = LayerEntity(4, 0, 1, "中部");
% =========================
out{end+1} = LayerEntity(5, 0, 1, "近畿");
% =========================
out{end+1} = LayerEntity(6, 0, 1, "中国");
% =========================
out{end+1} = LayerEntity(7, 0, 1, "四国");
% =========================
out{end+1} = LayerEntity(8, 0, 1, "九州");
end
end
end