ラベル UML の投稿を表示しています。 すべての投稿を表示
ラベル UML の投稿を表示しています。 すべての投稿を表示

2008年9月18日木曜日

酒飲み父ちゃんのアルコール量チェック (2) – Python で実装

酒飲み父ちゃんのアルコール量チェック – UML でクラス図の概略を作成」の続き。

 

具体例をシンプルに計算

前回、 クラス図の概略を作成した。はじめにこのクラス図は横に置いておき、具体例をシンプルに計算してみる。例えば、

今日、父ちゃんが「日本酒 : 1 合、焼酎 : 0.5 合、ウイスキー : 30 ml 」飲んだとする。それぞれのアルコール度数を「 15 度,  25 度,  40 度」とした場合、全部で日本酒で換算すると何合飲んだか?

ん~、暗算では無理。(+_+)

これを計算するには、すべての単位を ml に換算して、

# 日本酒 1 合のアルコール量
ALCOHOL_PER_SAKE_1GOU = 180 * 15 / 100

# 飲酒
sake    = 1 * 180 * 15 / 100        # 日本酒     : 1 合
shochu  = 0.5 * 180 * 25 / 100      # 焼酎       : 0.5 合
whiskey = 30 * 1 * 40 / 100         # ウイスキー : 30 ml

# 飲酒量を日本酒で考えると
print round((sake + shochu + whiskey) / ALCOHOL_PER_SAKE_1GOU, 2)

翌日に再度、同じように計算したい場合は、「飲酒」の変数に設定した計算式の中の値を変更。別の種類の酒を飲んだら、新しく変数を追加。日本酒換算ではなくて、焼酎換算にしたい場合は、最初の ALCOHOL_PER_SAKE_1GOU  の式を変更。新しい単位が追加された場合は、単位と ml への換算の式は頭の中に記憶しておくか、または別の管理方法を考えなくてはいけない。 この方法は、あくまでも一例を計算するだけ。

 

ちょっとまとめる

翌日もう少し入力しやすいように、変更がありそうなところをまとめておく。 単位 ・ml への変換やアルコールの度数の情報を一箇所で管理、アルコール量の計算で毎回度数を 100 で割るという手間をなくしたり。とりあえず、クラスを使わないで書くなら、

# 酒 : アルコール度数
LIQUOR_LIST = {u'日本酒'     : 15,
               u'ウイスキー' : 40,
               u'焼酎'       : 25}

# 単位 : 各単位は ml へ変換するときに乗算する値
UNIT_LIST = {'ml'   : 1,
             'l'    : 1000,
             u'合'  : 180}

def alcoholPer1Unit(liquor, unit):
    u""" 指定された酒の1単位当たりのアルコール量を返す """
    return UNIT_LIST[unit] * LIQUOR_LIST[liquor] / 100.0

def quantityOfAlcohol(liquor, val, unit):
    u""" アルコールの量を単位 ml で返す """
    return val * UNIT_LIST[unit] * LIQUOR_LIST[liquor] / 100.0

def totalQuantityOfAlcohol(drinksOfToday):
    u""" 今日の飲酒量からトータルのアルコール量を返す """
    return sum([quantityOfAlcohol(x[0], x[1], x[2]) for x in drinksOfToday])

def isOk(quantityOfAl, limit):
    u""" 一日の許容アルコール量を超えていないかチェックする """
    return u"!!! 飲みすぎ !!!" if quantityOfAl > limit else ""

def printResult(drinksOfToday):
    today = totalQuantityOfAlcohol(drinksOfToday)
    print u"%s に換算すると %.2f %s 飲んだ" % \
            (LIQUOR, today / alcoholPer1Unit(LIQUOR, UNIT), UNIT)
    print isOk(today, quantityOfAlcohol(LIQUOR, LIMIT, UNIT))

# 飲酒制限
LIQUOR = u'日本酒'; LIMIT = 2; UNIT = u'合'

# 今日の飲酒量
drinksOfToday = [(u'日本酒', 1, u'合'),
                 (u'焼酎' , 0.5, u'合'),
                 (u'ウイスキー', 30, u'ml')]
printResult(drinksOfToday)

 

あ~、もうこれでいいような気もしてきた ^^; UI を被せるにはまだ足りないけど、計算する分にはこれでいいか。

 

クラス図に基づいて

UML で概略書いたので、それに基づいて実装してみる。図とはちょっと違うけれど ^^;

import logging
from decimal import Decimal

class Liquor:
    u""" 酒 """
    def __init__(self, name, alcoholByPercentage):
        self.name = name            # 酒の名前
        # アルコールの含有量
        self.alcoholByPercentage = Decimal(str(alcoholByPercentage))

    def __str__(self):
        return name + " " + str(self.alcoholByPercentage)
    
    def setDefaultUnit(self, unit):
        u""" この酒で使われるデフォルトの単位を設定する。

        Drink#__call__ の呼出しに先立ち、単位を設定しておく。
        """
        self.unit = unit
        return self

class Quantity:
    u""" 量 """
    def __init__(self, number, unit):
        self.number = Decimal(str(number))      # 数値
        self.unit = unit                        # 単位

    def __cmp__(self, other):
        # 単位 ml に変換してから量を比較する
        return cmp(self.convertToMl(), other.convertToMl())

    def __str__(self):
        return "%.2f" % self.number + " " + self.unit.name + \
                " (%.2fml)" % self.unit.convertToMl(self.number)

    def convertToMl(self):
        u""" 量を単位 ml に変換して返す """
        return self.unit.convertToMl(self.number)
    
    def getUnit(self):
        u""" 単位を返す """
        return self.unit

class Unit:
    u""" 単位

    基準となる単位を ml とした。すべての単位は ml へと変換するときに
    乗算する値を持たなくてはいけない。
    """
    def __init__(self, name, valOfConvMl):
        self.name = name                # 単位名
        self.convertUnits = []          # 単位変換のリスト
        # 単位 ml へ変換するために乗算する値
        self.valOfConvMl = Decimal(str(valOfConvMl))  
        
    def convertToMl(self, number):
        u""" 指定された数をml へ変換する。 """
        return number * self.valOfConvMl

class Drink:
    u""" 飲酒 """
    def __init__(self, liquor, quantity=0):
        self.liquor = liquor            # 酒
        self.quantity = quantity        # 量
    
    def __str__(self):
        return u"%s, %-s" % \
                (self.liquor.name, self.quantity)
        
    def __call__(self, number):
        u""" 数値を指定し予め生成ておいた飲酒オブジェクトを元に新たに Drink
        オブジェクトを生成する。
        
        Drink オブジェクトを簡単に作るためのメソッド。この呼出しに先立ち、
        Liquor クラスだけが設定されていること。そのオブジェクトを使い、
        数値を設定するだけで、Drink オブジェクトが生成される。
        """
        assert self.liquor.unit is not None
        return Drink(self.liquor, Quantity(number, self.liquor.unit))

    def getAlcoholByMl(self):
        u""" 単位 ml でアルコールの量を返す """
        return self.getQuantityByMl() * self.getAlcoholPercentage()
        
    def getQuantityByMl(self):
        u""" 飲酒の量を単位 ml で返す """
        return self.quantity.convertToMl()

    def getAlcoholPercentage(self):
        u""" アルコールの割合を返す """
        return self.liquor.alcoholByPercentage / 100
    
    def getUnit(self):
        u""" 単位を返す """
        return self.quantity.getUnit()
    
    def getLiquorName(self):
        u""" 酒の名前を返す """
        return self.liquor.name
    
    def getAlcoholPerUnitByMl(self):
        u""" 1 単位当たりのアルコール量 を ml で返す """
        return self.getAlcoholByMl() / self.quantity.number

class DrinkList:
    u""" 飲酒のリスト """
    def __init__(self):
        self.drinks = []     # 飲酒のリスト

    def __str__(self):
        return u"--------- 飲酒リスト ---------\n" + \
            "\n".join(["%s" % drink for drink in self.drinks]) + "\n" + \
            "-"*30 + "\n" + \
            u"アルコール総量:%.2f ml" % self.getAlcoholByMl()
        
    def add(self, Drink):
        u""" 飲酒を飲酒のリストに追加する """
        self.drinks.append(Drink)
        return self
    
    def getAlcoholByMl(self):
        u""" 飲酒におけるアルコールの総量を単位 ml で返す """
        return sum([x.getAlcoholByMl() for x in self.drinks])

class AlcoholRestriction:
    u""" 飲酒制限

    ex. 「一日、日本酒で 2 合までなら飲んでいい」
    """
    def __init__(self, drink):
        self.drink = drink        # 上限となる飲酒
        
    def __str__(self):
        result = u"制限: 一日に %s で %s までなら飲んいいよ!" % \
                    (self.drink.getLiquorName(), self.drink.quantity)
        result += u"\n!!! 飲みすぎ!!!" if not self.permit() else ""
        result += u"\n%s %s に相当" % \
                    (self.drink.getLiquorName(),
                    Quantity(self.drinkList.getAlcoholByMl() / \
                                self.drink.getAlcoholPerUnitByMl(),
                            self.drink.getUnit()))
        return result
    
    def setDrinkList(self, drinkList):
        u""" 飲酒リストを設定 """
        self.drinkList = drinkList
    
    def getLimitOfAlcoholByMl(self):
        u""" 設定された飲酒の上限であるアルコール量を返す """
        return self.drink.getAlcoholByMl()
        
    def permit(self):
        u""" 指定された飲酒リストにおけるアルコールの量は許容されるか? """
        assert self.drinkList is not None
        if self.drinkList.getAlcoholByMl() <= self.getLimitOfAlcoholByMl():
                return True
        else: return False

# --- 酒-------------------------------------------------------
u""" 日本酒 """
LIQUOR_SAKE = Liquor(u"日本酒", 15)
u""" 焼酎 """
LIQUOR_SHOCHU = Liquor(u"焼酎", 25)
u""" ウイスキー """
LIQUOR_WHISKEY = Liquor(u"ウイスキー", 40)

# --- 単位 -----------------------------------------------------
# `ml' を量を比較する際の基準となる単位とする
u""" ml """
UNIT_ML = Unit(u"ml", 1)

u""" 合 """
UNIT_GOU = Unit(u"合", 180)
u""" 升 """
UNIT_SYOU = Unit(u"升", 1800)

# --- 量 -------------------------------------------------------
# ウイスキー・バーボンなどにおける特別な量の呼び方
u""" シングル """
QUANTITY_SINGLE = Quantity(30, UNIT_ML)
u""" ダブル """
QUANTITY_DOUBLE = Quantity(60, UNIT_ML)
u""" ジガー """
QUANTITY_JIGGER = Quantity(45, UNIT_ML)

# --- 飲酒 ------------------------------------------------------
u""" 飲酒オブジェクトを簡単に作成できるようにする。

ex. Liquor.DRINK_SAKE(2)      : 「酒を 2 合飲んだ」
    Liquor.DRINK_WHISKEY(100) : 「ウイスキーを 100 ml 飲んだ」
Drink#__call__() が呼出される。
"""

u""" 日本酒の飲酒を生成するためのオブジェクト
デフォルトの単位を「合」とする。
"""
DRINK_SAKE = Drink(LIQUOR_SAKE.setDefaultUnit(UNIT_GOU))
u""" 焼酎の飲酒を生成するためのオブジェクト
デフォルトの単位を「合」とする。
"""
DRINK_SHOCHU = Drink(LIQUOR_SHOCHU.setDefaultUnit(UNIT_GOU))
u""" ウイスキーの飲酒を生成するためのオブジェクト
デフォルトの単位を「ml」とした
"""
DRINK_WHISKEY = Drink(LIQUOR_WHISKEY.setDefaultUnit(UNIT_ML))


if __name__ == "__main__":
    # 飲酒は「日本酒で2合まで」という制約
    alcoholRestriction = AlcoholRestriction(DRINK_SAKE(2))
    # 飲酒リスト
    drinkList = DrinkList().add(
        DRINK_SAKE(1)).add(
        DRINK_SHOCHU(0.5)).add(                      # 焼酎 1 合
        Drink(LIQUOR_WHISKEY, QUANTITY_SINGLE))    # ウイスキーのシングル

    # 飲酒制限に飲酒リストを設定
    alcoholRestriction.setDrinkList(drinkList)

    # 飲酒リストを表示
    print "%s" % drinkList
    # 結果を表示
    print "%s" % alcoholRestriction

 

 

ユニットテスト

やってること単純だけれど、テストしながらじゃないと実装できない ^^;

import unittest
import Liquor
import logging
from decimal import Decimal

class TestLiquor(unittest.TestCase):

    def setUp(self): 
        # 単位
        self.unitGou = Liquor.Unit(u"合", 180)
        self.unitMl = Liquor.Unit(u"ml", 1)
        
        # 酒
        self.sake = Liquor.Liquor(u"焼酎", 25)
        self.whiskey = Liquor.Liquor(u"ウイスキー", 45)
        
        #--- 飲酒 ---
        # 焼酎を 1 合飲む
        self.d1 = Liquor.Drink(self.sake, Liquor.Quantity(1, self.unitGou))
        # ウイスキーを 100 ml 飲む
        self.d2 = Liquor.Drink(self.whiskey,
                                    Liquor.Quantity(100, self.unitMl))
        # 焼酎を 2 合飲む
        self.d3 = Liquor.Drink(self.sake, Liquor.Quantity(2, self.unitGou))
        # ウイスキーを 150 ml 飲む
        self.d4 = Liquor.Drink(self.whiskey,
                                    Liquor.Quantity(150, self.unitMl))

        # 飲酒リスト
        self.dl = Liquor.DrinkList()
        self.dl.add(self.d1).add(self.d2).add(self.d3).add(self.d4)
        
        self.dl2 = Liquor.DrinkList()
        self.dl2.add(self.d1)
        
        self.dl3 = Liquor.DrinkList()
        self.dl3.add(self.d3)
        
        # 飲酒制限
        self.alcoholRestriction = Liquor.AlcoholRestriction(
                                        Liquor.Drink(self.sake,
                                            Liquor.Quantity(2, self.unitGou)))
                                                        
    def tearDown(self): 
        pass
    
            
    def testAlcoholOfDrink(self):
        u""" 飲酒におけるアルコール量のテスト """
        # 焼酎を 1 合飲んだ場合、45 ml のアルコール
        self.assertEqual(45, self.d1.getAlcoholByMl())
            
    def testAlcoholOfDrinkList(self):
        u""" 飲酒リストのアルコール量をテスト """
        dl = Liquor.DrinkList()
        
        dl.add(self.d1)
        self.assertEqual(45, dl.getAlcoholByMl())

        dl.add(self.d2)
        self.assertEqual(90, dl.getAlcoholByMl())

        dl.add(self.d3)
        self.assertEqual(180, dl.getAlcoholByMl())

        dl.add(self.d4)
        self.assertEqual(Decimal("247.5"), dl.getAlcoholByMl())
        
        # 合で
        self.assertEqual(Decimal(str(1.375 * 180)), dl.getAlcoholByMl())
    
    def testQuantityOfDrink(self):
        u""" 1 合飲んだ焼酎の飲酒の量を ml で """
        self.assertEqual(45, self.d1.getAlcoholByMl())
        u""" 1 合飲んだ焼酎の飲酒の量を 合 で """
        self.assertEqual(45, self.d1.getAlcoholByMl())
        u""" 100 ml 飲んだウイスキーの飲酒の量を ml で """
        self.assertEqual(45, self.d2.getAlcoholByMl())
    
    def testAlcoholRestrictionSetting(self):
        u""" Alcoholrestriction クラスの設定 """
        self.assertEqual(90, self.alcoholRestriction.getLimitOfAlcoholByMl())

    def testCmpQuantity(self):
        # 単位が同じ場合
        self.assertEqual(0,
            cmp(Liquor.Quantity(10, self.unitGou),
                Liquor.Quantity(10, self.unitGou)))
        self.assertEqual(-1,
            cmp(Liquor.Quantity(9, self.unitGou),
                Liquor.Quantity(10, self.unitGou)))
        self.assertEqual(1,
            cmp(Liquor.Quantity(11, self.unitGou),
                Liquor.Quantity(10, self.unitGou)))
                
        self.assertEqual(True,
            Liquor.Quantity(10, self.unitGou) ==
            Liquor.Quantity(10, self.unitGou))
        self.assertEqual(True,
            Liquor.Quantity(10, self.unitGou) <=
            Liquor.Quantity(10, self.unitGou))
        self.assertEqual(True,
            Liquor.Quantity(10, self.unitGou) >=
            Liquor.Quantity(10, self.unitGou))
        self.assertEqual(False,
            Liquor.Quantity(10, self.unitGou) <
            Liquor.Quantity(10, self.unitGou))
        self.assertEqual(False,
            Liquor.Quantity(10, self.unitGou) >
            Liquor.Quantity(10, self.unitGou))
            
        self.assertEqual(True,
            Liquor.Quantity(9, self.unitGou) <
            Liquor.Quantity(10, self.unitGou))
        self.assertEqual(True,
            Liquor.Quantity(11, self.unitGou) >
            Liquor.Quantity(10, self.unitGou))

        # 単位が異なる場合
        self.assertEqual(0,
            cmp(Liquor.Quantity(10, self.unitGou),
                Liquor.Quantity(1800, self.unitMl)))
        self.assertEqual(-1,
            cmp(Liquor.Quantity(9, self.unitGou),
                Liquor.Quantity(1800, self.unitMl)))
        self.assertEqual(1,
            cmp(Liquor.Quantity(11, self.unitGou),
                Liquor.Quantity(1800, self.unitMl)))

        self.assertEqual(True,
            Liquor.Quantity(10, self.unitGou) ==
            Liquor.Quantity(1800, self.unitMl))
        self.assertEqual(True,
            Liquor.Quantity(10, self.unitGou) <=
            Liquor.Quantity(1800, self.unitMl))
        self.assertEqual(True,
            Liquor.Quantity(10, self.unitGou) >=
            Liquor.Quantity(1800, self.unitMl))
        self.assertEqual(True,
            Liquor.Quantity(9, self.unitGou) <
            Liquor.Quantity(1800, self.unitMl))
        self.assertEqual(True,
            Liquor.Quantity(11, self.unitGou) >
            Liquor.Quantity(1800, self.unitMl))
            
        self.assertEqual(False,
            Liquor.Quantity(10, self.unitGou) <
            Liquor.Quantity(1800, self.unitMl))
        self.assertEqual(False,
            Liquor.Quantity(10, self.unitGou) >
            Liquor.Quantity(1800, self.unitMl))

    def testAlcoholRestrictionPermit(self):
        u""" 飲酒リストのテスト """
        self.alcoholRestriction.setDrinkList(self.dl)
        self.assertEqual(False,
            self.alcoholRestriction.permit())
        
        self.alcoholRestriction.setDrinkList(self.dl2)
        self.assertEqual(True,
            self.alcoholRestriction.permit())

        # 2 合 ぎりぎり
        self.alcoholRestriction.setDrinkList(self.dl3)
        self.assertEqual(True,
            self.alcoholRestriction.permit())

        self.dl3.add(self.d1)
        self.alcoholRestriction.setDrinkList(self.dl3)
        self.assertEqual(False,
            self.alcoholRestriction.permit())
            
    def testAllcoholRestrictionPermit2(self):
        u""" Liquor モジュールにおいて定義してある簡便な方法をテスト """
        # 飲酒は「日本酒で2合まで」という制約決める
        alcoholRestriction = Liquor.AlcoholRestriction(Liquor.DRINK_SAKE(2))
        # 日本酒を 1 合飲んだ
        drinkList = Liquor.DrinkList().add(Liquor.DRINK_SAKE(1))
        # 飲酒制約を設定する
        alcoholRestriction.setDrinkList(drinkList)
        # 飲酒の総量は許容範囲か?
        self.assertEqual(True, alcoholRestriction.permit())
        # 焼酎を 0.7 合飲んだ
        drinkList.add(Liquor.DRINK_SAKE(0.7))
        self.assertEqual(True, alcoholRestriction.permit())
        # ウイスキーを 30 ml 飲んだ
        drinkList.add(Liquor.DRINK_WHISKEY(30))
        self.assertEqual(False, alcoholRestriction.permit())
        # 焼酎を 0.01 合飲んだ
        drinkList.add(Liquor.DRINK_SAKE(0.01))
        self.assertEqual(False, alcoholRestriction.permit())
        
            
if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG,
        format='%(asctime)s %(levelname)s ' + \
               '"%(pathname)s", line %(lineno)d, %(message)s')
    logging.getLogger().setLevel(logging.DEBUG)
    unittest.main()

2008年9月12日金曜日

酒飲み父ちゃんのアルコール量チェック – UML でクラス図の概略を作成

酒飲み父ちゃん

わが屋の父は酒飲みだ。大酒飲みというわけではない。酒好きと言った方が正確かな。ばぁちゃんの父ちゃんというのが大酒飲みだったらしく、その息子、つまり、父の伯父に当たる人は、もう臨終間際というとき、親戚中が酒を持ちより、大好きだった酒をたらふく飲んで亡くなったとう話を聞いたことがある。

家の父は、母の目を盗んで酒を飲む。母が所要で出かけるのを見計らっては、コソコソと活動をはじめる。階段を降りる足音。1 階の酒がしまってある扉の開く音がした。そして、しばらくして、風は南風。ベンランダから気持ちのよい風が吹いてくる。そんな中、「ぷ~ん」と香るウイスキーの匂い。あぁ、またやってるなと思い確認に行く。そしらぬ顔でパソコンをしている父。自分も何も気づかないふりをして、部屋の中を見回す。ウイスキーの入っているグラスの気配はない。既に証拠の隠滅を図ったようだ。しかし、さっきよりも強くなるアルコールの匂い。部屋の喚起までは手が回らなかったらしい。部屋へ戻り、携帯へと手をのばす。そう、母へ現状説明をするために…。

 

健康的な飲酒量

「一日に 2 合まで。一週間に 2 日は休肝日を作るのがいい。」と聞いたことがある。多分、父はこれ以上に飲んでいる。「焼酎」を水で薄めコップに 2 杯、それに「ウイスキー」が加わり、そして、「日本酒」もあればそれも飲む。本人は、それほど飲んでないと主張する。「水で薄めているし、量は少ない」と。たしかに、実際にどのくらい飲んでいるのか状況を把握しにくい。果たして、日本酒に換算した場合に何合飲んでいるのか疑問…。

計算しようと思えば、もちろん簡単にアルコールの量を求めることができる。まず、それぞれの酒のアルコール量を求める。

  • 飲んだ量 × アルコールの度数

日本酒 1 合 180 ml 、アルコール度数 15 度として、180 × 0.15 = 27 。これを上記の総量で割れば、日本酒に換算してどれだけ飲んだかわかる。この数値は、直観的にどれくらい飲みすぎているか把握しやすい。

 

知りたいこと

結局、自分が把握したいことは次のことになる。

  1. 想定している酒の上限より、飲みすぎていないか?
  2. 日本酒に換算すると、全部でどのくらい飲んだのか?

 

画面のイメージ

さて、上記をシンプルなアプリにするとしたら、まず、次のような画面が思い浮かぶ。

080912-001

左の画面で、予め、一日に飲んでよいアルコールの量を設定。右の画面では、実際に酒を飲んだときに、「酒の種類」「飲んだ量」「量の単位」を入力する。そして、それに対して上記の「知りたいこと」で書いた結果が表示。

 

酒の制限

もう少し具体的にイメージをしてみる。上記の左の設定画面においては、「アルコール量」を直接指定するのではない。「酒の種類」と、「それをどれだけ飲んでよいか」で指定することができること。このとき、単位も自由に設定。例えば、「日本酒」なら、「2 合」または「360 ml 」と設定することができるようにしたい。

 

飲酒の評価

右の画面は、酒を飲んだ後に、どれだけ酒を飲んだかを記入する。入力すると、左の画面で設定した制限を超えていないかを表示。超えている場合は、「飲みすぎ!」などと表示される。また、飲んだ量が、左で設定した酒と単位から考えて、どのくらい飲んだか表示されること。例えば、「酒の制限」において、「一日、日本酒で 2 合まで」と設定したら、日本酒で単位は「合」で換算した値が表示されること。

 

単位の設定

単位については、自由に設定ができるよう、下図のような設定画面があるとよい。左の列は単位の名前で、右の列はその単位を ml に換算するときに「乗算する値」を記入する。これは、単位が異なってもアルコールの量を比較可能にするためだ。基準となる単位として ml を特別視することにした。 (ただし、はじめからそのように考えていたのではなく、画面の概略を考えながら、また、クラス図を考えながら、そして、実装しながら最終的にそういう形にしようと考えた。)

080912-002

 

酒の設定

おっと、忘れるところだった。 (+_+) 当然ながら、酒を設定する画面も必要。入力する項目は、「種類」と「アルコール度数」。

080912-001

 

クラス図を考える

上記の要求と、画面のイメージよりクラス図の概略を考える。

 

まずは、「酒」クラス。「酒の種類」を設定する画面を想像しながら、必要な情報は「名前」「アルコールの割合」ということがわかる。

080912-002

 

単位

次に「単位」クラス。同じく画面のイメージより、「名前」「単位 ml への変換する場合に乗算する値」の情報が必要であることがわかる。単位を変換するための情報を持っているので、このクラスが単位を変換する操作を持つことにした。

080912-003

 

「単位」がでたので、次は「量」クラス。「量」は「数値」と「単位」クラスへの参照を持つことによって表現する。「量」は、比較可能でなくてはいけないので、ここで比較するためのメソッドを実装。比較の方法は、例えば、「日本酒 1 合」と「ウイスキー 60 ml 」をアルコール量の観点から判別をする。単位が異なっていても比較できるように、先ほど想定した「単位」クラスのメソッドを使い、単位を揃えた後に比較する。

080912-010

 

飲酒

これで「飲酒」クラスを表現できるようになった。「飲酒」は「酒」と「量」への参照を持つことによって表現。このクラスでは「酒」と「量」オブジェクトを使い、「飲んだ量に含まれるアルコールの量」を計算する操作を持つ。

080912-011

 

飲酒リスト

酒を飲むとき、一種類の酒だけを飲むわけではない。「飲酒」の画面を見てもわかるように、複数の酒を飲むことを想定している。その日に飲んだ酒全体でアルコール量がどうであったかを問題にしている。そのため、複数の「飲酒」を保持する「飲酒リスト」なるクラスを想定した。このクラスでは、個々の「飲酒」オブジェクトに問い合わせて、「アルコールの総量」を返す操作を持つ。

080912-012

 

飲酒制約

一番最初に想定した画面「飲酒の制限」。これに対応したクラスが「飲酒制約」。「飲酒」と「量」への参照を持つことによって、その量が一日に飲んでよい酒の量であることを表わす。「飲酒と量」は、それぞれ自分の好きな酒の種類と単位で設定できること。

このクラスは、飲酒制限についての情報を持っているので、「飲みすぎかどうか」を判定することができる。そのため、先ほどの「飲酒リスト」に対して、「飲酒リストが制限を超えていないか?」答えることができる。また、ここで設定した酒の種類と単位より、「その酒の単位当りで、どのくらいの量の酒を飲んだか?」ということにも答えることができるようにする。

080912-013

 

クラス図の全体

以下が全体のクラス図になる。今回、単位の変換を 「ml へと変換する」と固定したので、単位変換の実装がシンプルになりそうだ。ただし、そのような方法とるため、全ての単位において「ml への変換」するための値の設定は、ユーザが責任を負うことになる。実際のところ、それほど単位の数は多くないのでこれで十分に思われた。

080912-014

 

余談

できることなら、例えば以下のように単位の変換規則を設定したら、

  • 「合」から「升」へは 1/10 を掛ける
  • 「合」から「 ml 」へは 180 を掛ける

自動的に、「升」から「 ml 」へと変換できるようになるのが望ましい。つまり、

  • 「升」から「合」へ変換 「 10 を掛ける」
  • 変換規則を組み合わせて、「10 × 180 」を導き、値を適用

という動作が行なわれるとよい。今回は、この実装は後回しに、ユーザの設定に任せることにした。


関連記事

2008年2月19日火曜日

体調を記録するアプリケーションの作成 - UML で概念モデリング 2

前回、太郎くんの、日々の病気の症状の記録をモデル化することを考えた。ここで再度、太郎くんの記録しておきたい症状の例を挙げておく。

2007.4.1 : 今日も頭痛が朝から続いている。夕方には、腹痛も加わってきた。

2007.4.2 : 朝は昨日よりも頭 痛は和らいでいた。昼間、吐き気あり。夕方からは眩暈も加わってきた。

最後には、以下のようなモデルで「症状」の記録を表現した。080219-006 

 

特定の身体部位とは結びついてない症状

上記のモデルを作成するとき、「身体部位」と「感覚」の組み合わせによって表現される症状を中心に考えた。では、症状の記録の例にあった、「吐き気」「眩暈」はこのモデルで表現できるだろうか?

まず、「吐き気」について考えてみる。吐き気というのは、人が感じる感覚である。では、どこで吐き気を感じるのだろうか?喉だろうか、胃だろうか、それともその両方だろうか?一般的に吐き気と言った場合、頭痛のように特定の身体部位と結びついていないように感じる。つまり、症状を感じる身体部位がハッキリとしていない。

「眩暈」はどうだろう?眩暈は感覚だ。では、目で感じているのだろうか?それとも、脳だろうか?頭だろうか?やはり、「吐き気」と同じく、ハッキリとどこで感じるか身体部位を特定できないように思える。

こういった症状があった場合、上記のモデルでは対応できない。そこで、以下のように変更した。

 080219-002

「吐き気」「眩暈」のように、特定の部位と結びついていない症状を表現するために、「症状の種類」というスーパータイプを想定した。これまで考えてきた、「頭痛」「腹痛」のような、特定の身体部位と感覚によって表現される症状は、そのサブタイプとして「身体の局所的な症状の種類」というクラスを作成した。身体の部位を特定できる症状は、症状の中でも特殊な症状として考える。

 

定期的に記録したい症状と、突発的な症状

病気において、症状というのは、ある程度中心となるものがある。定期的にある症状と、たまにしかない突発的な症状に分かれる。太郎くんの場合、頭痛は毎日ある。中心となる症状のため、「朝」「昼」「晩」と毎日どのくらい痛みの程度が変化しているか記録している。それに対して、手や足の痛みはめったにない。また、頭痛以外の体の痛みも記録しているが、中心となる症状からは、ややはずれるので、一日トータルで考え、どの程度であったか評価し、記録すれば十分であるように感じている。

そこで、症状の中でも定期的にチェックして記録するものと、そうではないものを分けて考えていくことにした。また、症状をカテゴリに分けて見ることができるように、症状を分類できるようにする。例えば、「眩暈」「吐き気」は「脳神経症状」として分類できるようにしたい。

以下が、上記を踏まえたクラス図になる。

20080219

「症状のカテゴリ」は、あるカテゴリーが複数のカテゴリーに所属しないように、ツリー構造となるようにした。

「定期的な症状のチェック」は、特定の期間において、どの症状をどの時間帯で評価するのか定めるためのものである。期間の属性を持ったのは、定期的に評価したい症状が変化する可能性があるためだ。

CCC

 

関連記事

2008年1月21日月曜日

ブログに見られる人間関係を UML で概念モデリング 1

ブログには、網の目状に広がる人間関係の連鎖が見られる。知合いのブログを見ていると、コメントやトラックバックに、その人を中心とした人のつながりが見えてくる。

Google Reader のような RSS リーダーを使っていると、「自分から見たブログ同士のつながりを記録できたらいいのになぁ」と思うことがある。例えば、ある人のブログの RSS を読んでいて、同じ日付にその人の知合いは何をして何を考えていたのがわかったり。

以下のようなインターフェイスを持ったアプリがあったらおもしろい。各四角はブログを表わしており、同じ日付のブログの内容を眺めることができるとする。

080120-011

仲のよい友人とだけ、人間関係のネットワーク図を共有し、「これこれこういうつながりのあるあの人が、こんなこと言っているけど、どうなんだろう?」という具合な話ができるかもしれない。それをブログのような形にすれば、特定の人間関係のレイヤーにのみ存在するコミュニケーションが Web 上で可能になる。その他にも、「関心の対象となるブログを書いている人」の「友だちのパートナー」の一覧を取得して、その人たちの物の考え方や感じ方を比較してみたり、いろいろできそう。

結局、おもしろそうな点は何かと言えば、特定の点 (関心対象のブログ) から、ネットワークを辿り、そこにおける集団がどのようなことを考えているか把握できること。特定の点を考えずに抽出すると、その点 (人) というバイアスがかからず、平坦で均一化された情報に目が行ってしまう。特定の点があるからこそ、情報が歪み、その歪みがおもしろさをもたらす。

話が拡散してズレてしまった。 (+_+) モデリングしよう。

 

080115-004A くんは、毎日、 B  さんのブログを読んでいる。B さんのブログには、たくさんのコメントが寄せられる。コメントをしている人の中には、自分でブログを持っている人もおり、B さんの実生活における知合いも含まれている。

A くんは、B さん経由でいろいろな人のブログを読むようになった。例えば次のような場合がある。

  • C さんは、B さんの高校時代の友人である。
  • D さんは、C さんの旦那である。

B, C, D の 3 人のブログを読んでいるときは、その人のハンドルネームを見れば、B さんとどのような関係なのかすぐにわかった。

080115-005

しばらくして、徐々に興味の対象が広がってきた。

  • E さんは、C さんの妹で、Bさんにとっては中学時代の友人 (部活の後輩) である。ブログの内容がともておもしろい。
  • F さんは、B さんのブログによくコメントをしているが、B さんとの関係はわからない。
  • D さんは、もう一つ別のブログを別のハンドルネームではじめた。

080115-006

 

要求

このように、網目のように読む対象が増えてきてしまった。今では、ブログにおけるハンドルネームと、誰がどういう関係なのかわからりにくくなってしまった。今後も増加していくと考えられる。これを把握できるように整理したい。

 

分析

人とブログ

080116-005最初は、「人」と「ブログ」との関係。

「D さんは、もう一つ別のブログを別のハンドルネームではじめた。」

「E さんは、C さんの妹で、中学時代の友人であり、いつも記事がおもしろい。 」

上記の例より、「人」は複数の「ブログ」を持つことができる。

また、ブログに対して「おもしろさ」という観点から評価していることより、ブログに対する評価項目が必要となる。右の図は、それを表現したクラス図。

 

抽象的な「関係」

人と人との関係には、「友人」「夫婦」「姉妹」などのように種類がある。これを示したのが下図の左側。

関係の種類として「不明」があるが、これは「F さんは、B さんのブログによくコメントをしているが、B さんとの関係はわからない。 」という場合があるためだ。

080116-003 080116-005

人と人との関係は、「高校時代の友人」や「中学時代の親友」と言うように、ある一定の時期 (期間) 形成さされる。人の一生において、特定の時期には「幼児期」や「思春期」のように呼び名がある。時期という視点で種類に分け、これを表わしたのが上図の右側。

 

具体的な「関係」

080116-002

上記の関係が「抽象的な関係」だとすると、個別の人と人の間で成立する「具体的な関係」というものを考えることができる。例で述べた

「C さんは、B さんの高校時代の友人である。」

という文をオブジェクト図で表現したのが左図である。

ここでは、「関係」というオブジェクトが中心となって B さんと C さんをつなげている。この「関係」は、B さん・ C さんの間においてのみ成立している特定の「関係」を表わしている。「友人」などの「関係の種類」という抽象レベルから見ると、一段階、具体的なものと言える。

 

具体例でチェック

例で挙げれられていた関係を、全て表現できるか確かめてみた。

080117-001-2

  • C さんは、B さんの高校時代の友人である。
  • D さんは、C さんの旦那である。
  • E さんは、C さんの妹で、Bさんにとっては中学時代の友人 (部活の後輩) である。ブログの内容がともておもしろい。
  • F さんは、B さんのブログによくコメントをしているが、B さんとの関係はわからない。
  • D さんは、もう一つ別のブログを別のハンドルネームではじめた。

 

ここで表現されていないのは次の点である。

  • D さんは、C さんの「旦那」であるか判別できない
    • →  「人」の属性に「性別」の項目を加える。
  • E さんは、C さんの姉か妹かわからない
    • → 「人」の属性に「年齢」の属性を加える。または、姉妹の分類を分け、「関係」に方向性を持ったサブクラスを作る。( ← 今、思いついたので、あまり検討してない ^^; )

 

とりあえず、要求に対して必要性の低いものは、置いておくことにする。

 

クラス図

ここまでをクラス図にしたのが以下のもの。

080121-001

「関係」の「時期の種類」に対する多重度が「多」なのは、ある関係が複数の時期に渡って継続することがあるためである。

 

続く... CCC

2008年1月4日金曜日

体調を記録するアプリケーションの作成 - UML で概念モデリング 1

使用するツール

JUDE/Community

 

現状

「太郎」さんは、病気になってから、いろいろな症状が毎日続いている。日記のように毎日記録をつけているが、単にテキストとして保存しているだけ。同じような症状が繰り返すこともあり、毎日記録をつけるのが面倒だ。

例えば、次のように記録している。

2007.4.1 : 今日も頭痛が朝から続いている。夕方には、腹痛も加わってきた。

2007.4.2 : 朝は昨日よりも頭 痛は和らいでいた。昼間、吐き気あり。夕方からは眩暈も加わってきた。

 

要求

毎日記録をつけて、症状がどのように変化しているか把握したい。現状では、日記を読み返して、過去にあったことを定期的に把握している。漠然とした傾向をイメージするだけである。

 

分析

最初に、関心の中心である「体調」という言葉について考えた。この言葉の日常的な用法は二つある。

  • (A) 「今、ちょっと体調が悪い」
  • (B) 「昨日から体調を崩している」

(A) は、ある時点における「具合」を表わしている。それに対して、 (B) は、特定の時点から現時点まで、一定の期間の状態を表わす。

071231-023

日常的に使われる言葉は、コミュニケーションが成立すればよいので、曖昧さを含んでいても問題はない。しかし、状態をデータとして管理する際には、それではちょっと具合が悪い。

太郎さんは、日記のように、毎日体調を記録する目的で「体調」という言葉を使っている。そこで、ここでは「体調」という言葉を 、次にようにとらえることにする。

  • 一日を単位とした体の状態

体調を、一日に生じる様々な「症状」によって把握する、トータルな状態を表わす概念として見なして考えた。

これをクラス図にしてみた。

080101-025

上記の例として挙げた記録のオブジェクト図を描いてみた。

080101-026

このモデルでは、テキストとして記録しているものと比べると、それほど変わらない。違うのは、各症状を特定の基準で個別に表現しているところ。

 

「症状」について

症状には「頭痛」「腹痛」「吐き気」などのように種類がある。また、「朝から頭痛がした。」のように、「朝」「昼」「夕」のような時間帯と関係している。これを表現したのが下図。

080101-029

記録内容における「症状の種類」「時間帯」以外の要素については、とりあえず症状の「備考」属性に記録することによって対応することにした。

例では書かれていないが、例えば、頭痛の痛さの程度を 5 段階で自己評価して記録することより、症状を比較することができるようになる。そこで、症状に「程度」という属性を加えた。また、「頭痛が朝から晩まで続いていた。」のように、症状の種類は複数の時間帯と関係するので、一つの症状に対して複数の時間帯を設定できるようにした。

上記をオブジェクト図で表現してみた。

080101-027

これを見ると「程度」という属性を入れた意義がわかる。例えば、上図における 2007.4.2 における「頭痛」の「備考」属性の値 「昨日よりも和らいだ」 は、「程度」属性で表現できる。つまり、2007.4.2 における「頭痛」の値を、2007.4.1 の頭痛の程度よりも、大きい値が設定することだ。

 

「症状」の表現

症状という言葉は、思ったよりも複雑。 (+_+) 例えば、「頭痛」について考えてみよう。頭痛とは、「」が「痛い」ことである。「腹痛」ならば、「」が「痛い」ことだ。同様に、腰痛ならば、「」が「痛い」ことである。「頭が痛い」と一口に言っても、例えば、頭が「しびれる」ような感じがしたり、頭が「締め付けられる」ような感じがすることがある。つまり、症状というのは、「頭」や「腹」のように、「身体部位」と、「痛い」や「しびれる」のように、「感覚」の組み合わせによって表現できる。

080101-030

おもしろいことに、日常に使っている言葉には、「頭」が「痛い」ことを「頭痛」と一単語で呼ぶことができるが、日本語では「頭」が「しびれる」ことを「頭しびれ」というようには言わない。 これは、「頭が痛い」という症状が圧倒的に多く、「頭がしびれる」ということが少ないため、そのような言葉をわざわざ一単語で表現する必要性がなかったためだろう。 オブジェクト図で表わすと、次のようになる。

080101-031

では、ここで症状の種類を変え、「背部痛」について考えてみることにする。背部痛の症状として、「肩甲骨の内側が痛む」や、「胸椎の辺りが痛い」という場合がある。これは「背部痛」が、背部という範囲よりも細かな部位に生じるということだ。先ほどのモデルに即して言えば、「背部」と呼ばれる身体部位に、「肩甲骨の内側」と「胸椎の辺り」という部分があるということ。そして、それぞれの痛みは伴に背部痛に分類される。これは、症状の範囲に全体と部分の関係があり、部位の分類が階層化されていることを示す。これをクラス図にして表現すると、次のようになる。

080101-032

上記をオブジェクト図で表現してみた。

080101-034

「腰椎の辺り」が「痛い」症状があった場合、「背部」へのリンクを辿ることで、「背部痛」の一種であることが表現されている。

 

関連記事