본문 바로가기

Sencha Touch 2

6일차 : Sencha Touch 2 class system!

Sencha 에서는 class system 을 사용한다. 

JAVA 를 공부하신 분들은 이미 알겠지만, 센차에서 class 는 JAVA 에서의 그 것과 유사하다.

그러므로 센차에서 class 역시 function 과 properties 를 가진다. 아래의 예를 보자.



1. 정의.

Ext.define('Animal', {
    config: {
        name: null
    },

    constructor: function(config) {
        this.initConfig(config);
    },

    speak: function() {
        alert('grunt');
    }
});


위의 예에서 config 내부에 name 이라는 properties 가 선언되 있으며,

function 으로는 speak 이 있음을 알 수 있다. 



Animal 이라는 class 를 define 했으니 해당 class 로 instance 를 하나 만들어 보자.

var bob = Ext.create('Animal', {
    name: 'Bob'
});

bob.speak(); //alerts 'grunt'

이렇게 만들고 난 뒤, speak 을 하게 되면 grunt 라는 내용의 alert 가 뜰 것이다.(아래에서 결과를 확인해 보자)


이제는, Animal class 를 상속해서 Human 이라는 class 를 만들어보자.

Ext.define('Human', {
    extend: 'Animal',

    speak: function() {
        alert(this.getName());
    }
});




2. 상속. (위의 내용과 이어짐)


Animal class 를 extend 한 게 보일 것이다.

그런데 기존의 Animal class 에 있었던 speak 을 재정의(override) 했다. 

그런데 재정의한 내용의 몸체에 보면 getName 이라는 function 이 있다.

우리는 이를 구현한 기억이 있는데 쓸 수 있는가? 쓸 수 있다!! 

그 이유는 잠시 후에 설명하겠다! 일단 Human 이라는 class 를 사용하여 instance 를 하나 만들어 보자.


var bob = Ext.create('Human', {
    name: 'Bob'
});

bob.speak(); //alerts 'Bob'

결과를 확인 해 보자. 

버튼 두 개를 만들어서 하나의 버튼을 눌렀을 때는 Animal class 의 instance 를 만들게 하고, 

다른 버튼을 눌렀을 때는 Human class 의 instance 를 만들게 한다. 

아래 코드는 위의 실험을 위해 sencha generate 를 통해 생성된 파일에서 main.js 를 수정한 것이다.



Ext.define('Animal', {
    config: {
        name: null
    },

    constructor: function(config) {
        this.initConfig(config);
    },

    speak: function() {
        alert('grunt');
    }
});

Ext.define('Human', {
    extend: 'Animal',

    speak: function() {
        alert(this.getName());
    }
});


Ext.define("Sample.view.Main", {
    extend: 'Ext.tab.Panel',
	
    requires: [
		Ext.TitleBar
    ],
	
    config: {
        tabBarPosition: 'bottom',

        items: [
			{
				xtype: 'panel',
				iconCls: 'home',
				defaults: {
					flex: 1
				},
				layout: {
					type: 'hbox',
					pack: 'center',
					align: 'stretch'
				},
				items: [
					{
						xtype: 'button',
						text: 'Animal',
						handler: function(button){
							var bob = Ext.create('Animal', {
								name: 'Bob'
							});

							bob.speak(); //alerts 'grunt'
						}
					},
					{
						xtype: 'button',
						text: 'Human',
						handler: function(button){
							var bob = Ext.create('Human', {
								name: 'Bob'
							});

							bob.speak(); //alerts 'Bob'
						}
					}
				]
			}
        ]
    }
});

해당 부분을 수정하고, app 을 실행했을 때, Animal 버튼을 눌렀을 때와 Human 버튼을 눌렀을 때를 차례대로 보여주고 있다.


- Animal 눌렀을 때


- Human 눌렀을 때





3. 자동 생성되는 config 옵션

여기서 우리가 다시 주목할 부분은 getName 을 사용한 부분인데, 지난 시간에도 언급했지만 이는 센차가 자동으로 generate 한 부분이다.
센차의 class system 에서는, config 에 지정된 속성에 대해서 몇 가지 작업을 해 준다. 

- a getter function that returns the current value, in this case getName(). 속성의 현재 값을 리턴하는 getter 함수 생성.
- a setter function that sets a new value, in this case setName(). 속성의 현재 값을 바꾸는 setter 함수 생성.
- an applier function called by the setter that lets you run a function when a configuration changes, in this case applyName(). 속성 값을 바꾸어 주려고 할 때 호출 되는 apply 함수 생성
- a updater funciton called by the setter than run when the value for a configuration changes, in this case updateName(). 속성 값이 바뀌었을 때 호출 되는 update 함수 생성

등의 작업을 해 준다. 

getter setter 는 어려운 내용이 아니니, 바로 apply 함수와 update 함수에 대해 알아보자.






4. apply & update 함수


아까 정의 했던 Human class 를 아래와 같이 수정해 보자. (수정 후 꼭 테스트 해 보길 바란다. 특히 Human 버튼을 클릭해 보길 바란다.)


Ext.define('Human', {
    extend: 'Animal',
	
	speak: function() {
        alert(this.getName());
    },

    applyName: function(newName, oldName) {
        return confirm('Are you sure you want to change name to ' + newName + '?')? newName : oldName;
    },
	
	updateName: function(name){
		alert("updated name is " + name);
	}
});

이렇게 만들 경우, name 값이 바뀔 때 마다 applyName 이 호출된다. applyName 내부적으로 진짜 이름을 바꿀 것인지를 물어본다.

만약 확인을 선택하면 새 이름으로 바뀌게 되고, 취소를 누르면 이전 이름이 유지가 된다. 

apply 함수는 리턴을 꼭 해줘야 한다. 그래야 새로운 값이 적용된다.


updateName 은 실제로 name 의 값이 바뀐 경우에만 호출된다. 그러므로 apply 에서 만약 취소를 눌러서 이전 값을 그대로 유지하기로 했다면 updateName 은 호출되지 않을 것이다. 


apply 와 update 함수를 사용할 때, 중요한 것은 실제로 instance 가 만들어지고 난 뒤, setter 를 호출하여 값을 바꿀 때에도 물론이거니와! 처음 create 될 때에서 apply 와 update 함수가 호출 된 다는 점이다.


지금까지 우리가 배운, 센차에서 제공하는 class 시스템을 정리하면 아래와 같다.


All classes are defined using Ext.define, including your own classes : 모든 class 들은 Ext.define 을 통해 정의된다.

Most classes extend other classes, using the extend syntax : extend 문법을 통해 모든 class 들은 다른 클래스를 상속한다.

Classes are created using Ext.create, for example Ext.create('SomeClass', {some: 'configuration'}) : class 들의 instance 는 Ext.create 를 통해 생성된다.

Always usine the config syntax to get automatic getters and setters and have a much cleaner codebase : 자동으로 getter 와 setter 을 생성하기 위해서는 config 문법을 사용하라!



5. Dependencies and Dynamic Loading (의존성 & 동적 로딩)

대부분의 class 는 다른 class 를 의존한다. 위에서 정의한 Human class 의 경우에도 상속한 Animal class 를 의존하다고 볼 수 있다. 때때로 우리가 정의한 class 내부적으로 다른 class 를 사용할 수 있다. 이럴 경우, 해당 class 가 load 되있어야 하고 그렇지 않을 경우 load 되도록 만들어야 한다. 그런 일을 하는 것이 바로 requires 문법 이다. 

Ext.define('Human', {
    extend: 'Animal',

    requires: 'Ext.MessageBox',

    speak: function() {
        Ext.Msg.alert(this.getName(), "Speaks...");
    }
});


위처럼 Human class 를 정의 했다면, Human class 로 instance 를 하나 만들 때, 센차는 requires 에 명시된 Ext.MessageBox 가 메모리에 load 되 있었는지 확인한다. 그리고 만약 load 되 있지 않다면 비 동기적으로 load 시킨다. 그런데 Ext.MessageBox 는 또 다른 class 에 의존하고 있을 수 있으므로, Ext.MessageBox 가 의존하는 class 들을 백그라운드에서 자동적으로 laod 해 준다. 이런식으로 모든 관련된 class 들이 load 되면 비로소 Ext.create 를 통해 Human class 의 instance 를 만들 수 있다. 


의존성 문제가 중요한 이유는 개발 모드에서는 그냥 개발해도 문제가 없는데, 실제 마켓에서 제공할 product 의 경우, 모든 class 를 다 load 한다면 속도면에서 문제가 될 수 있다. 인터넷에서 files 을 로딩하는 것은 속도가 걸리기 때문이다.

이런 경우에 우리가 구현한 app 에서 사용하는 class 들 로만 구성된 단 하나의 자바스크립트 파일이 있으면 좋을텐데 하고 생각할 법 하다. 실제로 JSBuilder 라는 tool 이 그런 일을 해준다. 자세한 것은 나중에 posting 하도록 하겠다.




6. Naming Conventions

1) classes

클래스 이름 지을 때, 오직 숫자나 알파벳으로만 작성해야 한다. 하이픈이나 underscores 를 사용할 수 없다.

.(dot operator) 를 통해 패키지 형식으로 class 이름을 주는데, 모든 클래스의 top level 의 이름은 같아야 한다.


  • MyCompany.useful_util.Debug_Toolbar is discouraged / 안되는 경우 - 하이픈이 있기 때문에
  • MyCompany.util.Base64 is acceptable / 되는 경우

  • 위의 예 처럼 top-level 이름은 MyCompany 로 같음을 알 수 있다.

    top level 의 name space 와 실제 class 이름은 camel case 형태여야 한다. (camel case : 낙타표기법이라 하는데 두 개 이상의 단어를 붙여서 쓰며 이 때 각 단어의 첫 글자는 대문자로, 나머지는 소문자로 쓴다. 그러나, 연결된 단어들 중에서 가장 처음 단어의 처음 알파벳은 대문자/소문자 중 하나로 표현할 수 있다. "LaBelle", "BackColor", or "iPod".) 그 외는 모두 소문자로 쓴다. 아래를 참조.


    MyCompany.form.action.AutoLoad


    축약어 역시 동일한 방식이다.

  • Ext.data.JsonProxy instead of Ext.data.JSONProxy
  • MyCompany.util.HtmlParser instead of MyCompary.parser.HTMLParser
  • MyCompany.server.Http instead of MyCompany.server.HTTP



  • 2) Source Files

    class 이름과 class 를 저장한 자바스크립트 파일의 실제 위치는 같아야 한다. 아래 예를 보자.


  • Ext.mixin.Observable is stored in path/to/src/Ext/mixin/Observable.js
  • Ext.form.action.Submit is stored in path/to/src/Ext/form/action/Submit.js
  • MyCompany.chart.axis.Numeric is stored in path/to/src/MyCompany/chart/axis/Numeric.js
  • path/to/src 여기는 실제 파일들이 위치하는 곳의 시작 폴더를 의미 한다.




    3) Methods and Variables

    메소드와 변수 이름 역시 alpahumeric 만 사용해야 하며, CamelCase 여야 한다.


    Acceptable method names: 가능한 메소드 이름들

    encodeUsingMd5()
    getHtml() instead of getHTML()
    getJsonResponse() instead of getJSONResponse()
    parseXmlContent() instead of parseXMLContent()



    Acceptable variable names: 가능한 변수 이름들

    var isGoodName
    var base64Encoder
    var xmlReader
    var httpServer



    4) Properties

    properties 도 메소드나 변수 이름 지을 때 처럼 똑같다.

    그러나 단 하나, static 한 properties 의 경우, 해당 properties 의 이름을 대문자로 써야 한다. 아래 예를 보자.


  • Ext.MessageBox.YES = "Yes"
  • Ext.MessageBox.NO = "No"
  • MyCompany.alien.Math.PI = "4.13"



  • 7. Statics

    JAVA 에서 처럼 class 자체에 properties 를 줄 수 있다. 아래 코드를 보자.


    Ext.define('Computer', {
        statics: {
            instanceCount: 0,
            factory: function(brand) {
                // 'this' in static methods refer to the class itself
                return new this({brand: brand});
            }
        },
    
        config: {
            brand: null
        },
    
        constructor: function(config) {
            this.initConfig(config);
    
            // the 'self' property of an instance refers to its class
            this.self.instanceCount ++;
        }
    });
    
    var dellComputer = Computer.factory('Dell');
    var appleComputer = Computer.factory('Mac');
    
    alert(appleComputer.getBrand()); // using the auto-generated getter to get the value of a config property. Alerts "Mac"
    
    alert(Computer.instanceCount); // Alerts "2"


    statics 공간에 instacneCount 와 factory 메소드가 있는 것을 볼 수 있다.


    그리고 config 속성에 brand 이름을 저장할 수 있도록 해 놨다. config 속성 내부에서 지정한 properties 들은 해당 class 의 instance 마다

    독립적으로 나타날 것이다.


    static 메소드인 factory 를 통해 두 개의 instace 를 생성한다.