《付録》dirComposite2.py

# -*- coding: utf-8 -*-
#===============================================================================
#    Copyright (C) 2000-2008, 小泉ひよ子とタマゴ倶楽部
#
# Change History: WPF examples
#    2008/01/25, IronPython 1.1.1 (download)
#    2008/08/22, IronPython 1.1.2 (download)
#    2008/03/16, ver.2.0, WPF
#    2008/00/00, ver.2.1, IronPython 1.1.2 
#===============================================================================
from _ant import *
from System.IO import *
from System.Windows import *
from System.Windows.Controls import *

clr.AddReference("System.Windows.Forms")
from              System.Windows.Forms import OpenFileDialog
from nt import listdir

## --------------------                # Composite::Component            
class FileSystem(object):
    def __init__(self, name):
        self.name = name

    def changed(self, client):        
        client.update(
            self.branch(self, 0),
            "%d bytes"%self.total())

    def branch(self, parent, indent):
        s = 
        for child in parent.items:
            s.append("%s+-- %s"%(" "*4*indent, child.name))
            child._branch(s, indent)
        return "".join(s)

    def _branch(self, s, indent):
        s.append(" [%d]\n"%self.total())

    def total(self):
        raise NotImplementedError("%s.total"
            %self.__class__.__name__)

## --------------------                # Composite::Composite            
class Directory(FileSystem):
    def __init__(self, name=None):
        super(self.__class__, self).__init__(name)
        self.items = 

    def _branch(self, s, indent):
        super(self.__class__, self)._branch(s, indent)
        s.append(self.branch(self, indent+1))

    def total(self):
        return reduce(
            lambda acc, e: acc + e.total(),
            self.items,
            0)

## --------------------                # Composite::Leaf            
class File(FileSystem):
    def __init__(self, name, size):
        super(self.__class__, self).__init__(name)
        self.size = size

    def total(self):
        return self.size

## --------------------                         # Composite::Client            
class TreeBuilder:
    def __init__(self, root, path):
        self.clearNode(root)
        parent = Directory(self._tail(path))
        self.addNode(root, parent)
        self.branch(parent, path, 0)

    def _tail(self, path):
        return path.split("\\")[-1]
    
    def clearNode(self, parent):
        parent.items.Clear()
    
    def addNode(self, parent, child):
        parent.items.Add(child)

    def branch(self, parent, path, indent):
        dirs = listdir(path); dirs.sort()    
        for e in dirs:
            info = FileInfo("%s\%s"%(path, e))
            if self._isdir(info):
                child = Directory(e)
                self.branch(child, "%s\%s"%(path, e), indent+1)
            else:
                child = File(e, info.Length)
            self.addNode(parent, child)
            
    def _isdir(self, info):
        return str(FileAttributes.Directory) in str(info.Attributes)

## --------------------               
class ExWindow(Window):
    def __init__(self, Content=None, **args):
        self.InitializeComponent(Content)
        self.init()

    def InitializeComponent(self, Content):
        self.Content = LoadXaml(Content)
        
    def init(self):
        target = "button", "textView", "total",
        self._Controls(target)

        self.button.Click += self.click
        self.root = Directory()
        self.update("(^.^)", "0 byte")

    def _Controls(self, target):
        controls = xaml_controls(self)
        for e in target:
            setattr(self, e, controls[e])

    def click(self, sender, e):
        dialog = OpenFileDialog()
        dialog.ShowDialog()
        TreeBuilder(
            self.root,
            FileInfo(dialog.FileName).Directory.FullName)
        self.root.changed(self)

    def update(self, text, value):
        self.textView.Text = text
        self.total.Content = value

## --------------------               
if __name__ == "__main__":
    import sys
    xaml = sys.argv[1]
    win = ExWindow(
        Title=__file__,
        Width=240, Height=200,
        Content=xaml,
        )
    Application().Run(win)

## --------------------               

クラス File:Leaf

Composite::Leaf では、Composite::Leaf で規定されたプロトコルに従って、単一オブジェクトとしての機能を実現します。

## --------------------                # Composite::Leaf            
class File(FileSystem):
    def __init__(self, name, size):
        super(self.__class__, self).__init__(name)
        self.size = size

属性 self.size は、ファイルのサイズを管理します。

    def total(self):
        return self.size

ファイルのサイズ self.size を、そのままリターン値とします。


Previous|4/5|Next

Composite パターンを導入した後で

古典的な Composite パターンを導入した後の状態を示したのが、サンプルファイル dirComposite2.py です。


複合オブジェクトは、単一オブジェクトの集合体です。ここでは、FileSystem が、複合/単一オブジェクトに共通する特性を扱います。

class what how
File ファイルに固有の特性を扱います (親クラスから移動した)属性 size は、ファイルのサイズを管理します。
Directory フォルダーに固有の特性を扱います (親クラスから移動した)属性 items は、複数のファイル/フォルダーを管理します。属性 items に含まれる各要素を扱うときには、list で規定されたプロトコルに従います。

クラス Directory:Composite

Composite::Composite では、Composite::Composite で規定されたプロトコルに従って、複合オブジェクトとしての機能を実現します。

## --------------------                # Composite::Composite            
class Directory(FileSystem):
    def __init__(self, name=None):
        super(self.__class__, self).__init__(name)
        self.items = []

属性 self.items は、フォルダーに含まれる各ファイル/フォルダーを管理します。

   def _branch(self, s, indent):
        super(self.__class__, self)._branch(s, indent)
        s.append(self.branch(self, indent+1))

フォルダーのサイズを表わす文字列 super()._branch の後に、その部分木を表わす文字列 self.branch を追記します。

    def total(self):
        return reduce(
            lambda acc, e: acc + e.total(),
            self.items,
            0)

フォルダーに含まれる各項目(ファイル/フォルダー)のサイズ e.total() を合計したものを、リターン値とします。

2b)機能の実現:Composite

Composite::Composite では、複合オブジェクト(合成要素)に固有のプロトコルを規定します。
複合オブジェクトは、単一オブジェクトの集合体です。その集合体の中に複合オブジェクトがあると、複雑な入れ子構造を表現できます。複合/単一オブジェクトを区別せず「一様に」扱えるのが、Composite パターンの効能のひとつです。

クラス FileSystem:Component

## --------------------                # Composite::Component            
class FileSystem(object):

親クラスを object に設定すると、その子孫クラスでは、組み込み関数 super を利用して、先祖クラスのメソッドを再利用できます。

    def __init__(self, name):
        self.name = name
        self.size = 0
        self.items = []

属性 size/items は、子孫クラスに移動したので、不要になります。

    def branch(self, parent, indent):
        s = []
        for child in parent.items:
            s.append("%s+-- %s"%(" "*4*indent, child.name))
            child._branch(s, indent)
        return "".join(s)

各 child が、フォルダーかファイルかを判定する if/else が不要になるので、簡潔で見通しの良いコードを記述できます。

    def _branch(self, s, indent):
        s.append(" [%d]\n"%self.total())

どのようにして合計サイズを得るか total() は、子孫クラスに依存します。そのため、動的な問題解決が可能になります。

    def total(self):
        raise NotImplementedError("%s.total"
            %self.__class__.__name__)

ここで規定したメソッドを、子孫クラスで再定義しないと、実行時に例外 NotImplementedError を生成するとともに、そのクラス名を出力します。