1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

多摩科学技術高校Advent Calendar 2019

Day 21

(超初心者向け)D言語で考えるgetter/setterとproperty

Last updated at Posted at 2019-12-21

Javaのgetter/setter長くてUZEEEEE!!!!と思っていたら、こういう記事を見つけたのでちょっと考えさせられました。そこで大好きな(得意ではない)D言語でちょっと考えてみようと思います。

  • OpenJDK 1.8.0_222
  • gdc 8.3.0

#Javaのgetter/setterについて
多摩科技ACなので、一応オブジェクト指向とJavaの説明をします。本題はここからです。

オブジェクト指向はソフトウェア設計の指針の一つで、「あらゆる物はオブジェクト(関連するデータをひとまとまりにし、それに対して代入、変換、関数などを行える実体)である」という考え方のことです。
初めてその言葉を使ったアラン・ケイによれば、

  1. すべてはオブジェクトである。
  2. オブジェクトはメッセージの受け答えによってコミュニケーションする。
  3. オブジェクトは自身のメモリーを持つ。
  4. どのオブジェクトもクラスのインスタンスであり、クラスもまたオブジェクトである。
  5. クラスはその全インスタンスの為の共有動作を持つ。インスタンスはプログラムにおけるオブジェクトの形態である。
  6. プログラム実行時は、制御は最初のオブジェクトに渡され、残りはそのメッセージとして扱われる。

らしいです。
##アクセス権
オブジェクトの設計図とでも言うべきものがclassで、これを最初に書かないことには何も始まりません。

Test1.java
class Student{
    public String name;
    public int number;

    Student(String name, int number){
        this.name = name;
        this.number = number;
    }
}

public class Test1{
    public static void main(String[] args){
        Student taro = new Student("Teraoka Taro",27);
        Student jiro = new Student("Yamashita Jiro",35);
        System.out.println("taro's name:" + taro.name);
        System.out.println("jiro's number:" + String.valueOf(jiro.number));
    }
}

Java久々すぎてStringをstringって書いちゃったのは別のお話。

Studentは生徒データの設計図で、main内でnewしているtarojiroがその実体であるオブジェクト(インスタンス)です。
classがまとめているデータをメンバ
といいます。Student内のnamenumberもメンバ(メンバ変数)です。多分それぞれ氏名と生徒番号でしょう。
Studentクラスの中にStudent(string,int)というメソッドがあります。クラスと同名のメソッドをコンストラクタと呼び、オブジェクト作成時(今回はnew Student(〜)...のところ)で呼び出されるものです。

メンバ変数の前にpublicというキーワードが付加されています。これらはアクセス修飾子と呼ばれます。
いくつか種類があるのですが、とりあえず

  • public:どこからでもアクセスできる
  • private:自クラス内からしかアクセスできない。

だけ押さえておけばOKです。Test1.javaのname,numberをprivateにすると

Test2.java
class Student{
    private String name;
    private int number;

    Student(String name, int number){
        this.name = name;
        this.number = number;
    }
}
コンパイルしようとした
$ javac Test2.java
Test2.java:15: エラー: nameはStudentでprivateアクセスされます
        System.out.println("taro's name:"+taro.name);
                                              ^
Test2.java:16: エラー: numberはStudentでprivateアクセスされます
        System.out.println("jiro's number:"+String.valueOf(jiro.number));

てなことになります。

##カプセル化
もし全てのメンバがpublicだと、色々と面倒なことになります。
例えばTest1.javaについて、こんなこともできちゃいます。

taro.number = -23;
jiro.name = null;

生徒番号に負の値を使う学校なんて、性格ひん曲がりもいいところですね。ましてや名前がnullなんて馬鹿馬鹿しいにも程があります。
ともかく、プログラマのミス(もしくは悪意)によっては、このようなバグが起こるかもしれません。

そこで、メンバ変数は全てprivateとし、それにアクセスするために別のメンバメソッドを用意する方法が考えられます。

Test3.java
class Student{
    private String name;
    private int number;

    Student(String name, int number){
        this.name = name;
        this.number = number;
    }

    public String getName(){return name;}
    public void setName(String name){
        assert name != null;
        this.name = name;
    }

    public int getNumber(){return number;}
    public void setNumber(int number){
        assert number > 0;
        this.number = number;
    }
}

public class Test3{
    public static void main(String[] args){
        Student taro = new Student("Teraoka Taro",27);
        Student jiro = new Student("Yamashita Jiro",35);
        jiro.getNumber(-5)
        System.out.println("taro's name:" + taro.getName());
        System.out.println("jiro's number:" + String.valueOf(jiro.getNumber()));
    }
}
実行しようとした
$ javac Test3.java
$ java -ea Test3
Exception in thread "main" java.lang.AssertionError
	at Student.setNumber(Test3.java:18)
	at Test3.main(Test3.java:27)

ちゃんと再設定時に確認ができてますね(コンストラクタでもやれって話ですけど)。
このようなメンバメソッドをgetter/setterといいます。

##getter/setter長い
さて、試しにStudentクラスに国語、数学、英語の点数を追加してみましょう。

Test3add.java
class Student{
    private String name;
    private int number;
    private int japanese;
    private int math;
    private int english;

    Student(String name, int number){
        this.name = name;
        this.number = number;
    }   

    public String getName(){return name;}
    public void setName(String name){
        assert name != null;
        this.name = name;
    }   

    public int getNumber(){return this.number;}
    public void setNumber(int number){
        assert number > 0;
        this.number = number;
    }   

    public int getJapanese(){return japanese;}
    public void setJapanese(int japanese){
        assert japanese >= 0 && japanese <= 100;
        this.japanese = japanese;
    }   

    public int getMath(){return math;}
    public void setMath(int math){
        assert math >= 0 && math <= 100;
        this.math = math;
    }   
    public int getEnglish(){return english;}
    public void setEnglish(int english){
        assert english >= 0 && english <= 100;
        this.english = english;
    }
}

クッソなげぇ
もしこれが10教科とかになったら...地獄ですね。
というわけで冒頭の話に戻ります。

#D言語で
##property
とりあえずTest3.javaをproperty使って書き換えてみましょう。

test3.d
class Student{
    private string _name;
    private int _number;

    @property{
        void name(string _name){
            assert(_name!=null);
            this._name=name;
        }
        string name(){return _name;}

        void number(int _number){
            assert(_number>0);
            this._number=_number;
        }
        int number(){return _number;}
    }

    this(string _name, uint _number){
        this._name=_name;
        this._number=_number;
    }   
}

ん〜、コードの長さあんまり変わんなくね?
しかしpropertyによって、あたかも変数を直接いじっているような簡単操作ができます。

test3.d(続き)
void main(){
    import std.stdio;
    Student saburo=new Student("Mukai Saburo",18);
    saburo.name.writeln;
    saburo.number=-1;
    saburo.number.writeln;
}
実行
$ ./test3
Mukai Saburo
core.exception.AssertError@test3.d(16): Assertion failure
(以下略)

ちゃんとAssertErrorも出ました。

##invariant
しかしこのままではあんまりにあんまりです。そこで、メンバの不変条件を決定できるinvariantの存在を知ったので使ってみます。invariant内は以下の時に呼ばれます。

  • コンストラクタ実行後、デストラクタ実行前
  • メンバ関数の実行前と実行後
test4.d
class Student{
    private string _name;
    private int _number;

    invariant{
        assert(_name!=null);
        assert(_number>0);
    }   

    @property{
        void name(string _name){
            this._name=name;
        }
        string name(){return _name;}

        void number(int _number){
            this._number=_number;
        }
        int number(){return _number;}
    }   

    this(string _name, uint _number){
        this._name=_name;
        this._number=_number;
    }   
}

void main(){
    import std.stdio;
    Student saburo=new Student("Mukai Saburo",18);
    saburo.name.writeln;
    saburo.number=-1;
    saburo.number.writeln;
}
実行
$ ./test3
Mukai Saburo
core.exception.AssertError@test4.d(9): Assertion failure
(以下略)

上出来。
これなら当然コンストラクタについてもチェックできるので、そっちにassertを置く必要もありません。
ちなみにdmdのv2.081.0からこういう書き方もできるようになっているそうです。

invariant(_name!=null);
invariant(_number>0);

#参考
Qiita: 結局のところgetter/setterは要るのか?要らないのか?
dlang.org: Contract Programming

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?