MVC Sederhana untuk Pemula

Repost artikel yg sudah pernah saya posting di blog yang lama.

Kali ini saya jelaskan tentang cara membuat “struktur” MVC sederhana untuk menulis kode yang maintainable dengan memecah kode menjadi beberapa bagian berdasarkan fungsinya ( separation of concerns ).

Saya anggap Anda sudah cukup mengetahui dasar OOP dan mempraktekkannya, jadi saya nggak perlu menjelaskan lagi apa itu class, instance, static, getter/setter, dsb. Saya juga nggak menjelaskan apa itu custom events dan bagaimana cara membuat & menggunakannya karena sudah pernah saya jelaskan dalam artikel yang lain. Selain itu, Anda juga harus mengerti cara mendefinisikan & membuat custom class untuk objek di library.

Di sini saya membuat aplikasi sederhana berupa sebuah kotak yang posisinya bisa dikontrol oleh 4 tombol seperti berikut:

[kml_flashembed fversion="9.0.124" movie="http://masputih.com/files/SimpleMVC.swf" targetclass="flashmovie" publishmethod="static" width="320" height="400"]

Get Adobe Flash player

[/kml_flashembed]

Design Patterns

Design patterns (DP) adalah sekumpulan solusi generik untuk masalah-masalah yang umum ditemui oleh para developer dalam membuat aplikasi. Pattern berarti pola, artinya DP bukan berupa kode, tetapi berupa panduan yang implementasinya terserah developer yang menggunakannya.

Info yang lebih lengkap bisa Anda baca di wikipedia.

Model-View-Controller

MVC adalah meta-pattern , artinya MVC bukan merupakan pattern yang berdiri sendiri tapi merupakan kumpulan dari beberapa pattern & dalam buku Head First Design Patterns , MVC dimasukkan dalam kelompok compound-pattern.

Pada prinsipnya, implementasi MVC membagi kode ke dalam 3 bagian yaitu:

  • Model sebagai sumber data
  • View sebagai representasi data dan user interface
  • Controller yang berfungsi sebagai “otak” atau business logic yang memproses user input dan meng-update Model dan View ( kalau diperlukan ).

Apa keuntungan MVC ?

Sekilas MVC kelihatannya merepotkan karena kita harus menulis kode lebih banyak tapi keuntungannya adalah kode kita lebih maintainable karena kita bisa mengubah salah satu bagian tanpa harus mengubah bagian yang lain. Sebagai contoh, misalnya kita membuat aplikasi A yang memproses data berformat XML namun kemudian kita diharuskan menggunakan data berformat JSON. Dalam kasus seperti ini, kita cukup mengubah Model tanpa harus mengubah bagian yang lain.

MVC juga mempermudah debugging karena kita bisa memperkirakan bagian mana yang bermasalah tanpa harus membongkar seluruh kode yang sudah kita buat.

Jadi secara umum, keuntungan MVC jauh lebih besar daripada kerepotan yang ditimbulkannya. Di samping itu, kalo Anda nggak mau repot menulis berbaris-baris kode, mungkin menjadi programmer bukan profesi yang cocok buat Anda & sebaiknya Anda cari pekerjaan lain. ;-)

Struktur dasar

Di dalam direktori projek, saya buat 4 buah sub-direktori/package seperti gambar di bawah ini.

Direktori Project di FlashDevelop

Direktori Project di FlashDevelop

Berikut ini diagram dari aplikasi yang saya buat.

Class Diagram

Class Diagram

View & ViewEvent

Package view berisi class yang berhubungan dengan library symbol yaitu MainView dan ArrowButton. MainView bertugas menyiarkan ViewEvent jika salah satu tombol navigasi diklik. Event ini ditangkap oleh Controller yang kemudian meng-update Model. Selain itu MainView juga menampilkan posisi kotak kuning.

Kenapa saya membuat ViewEvent dan nggak membuat Controller yang langsung mendengarkan MouseEvent.CLICK yang disiarkan oleh tombol kontrol ? Karena saya ingin meminimalkan coupling antara MainView dan Controller sehingga jika saya perlu mengubah View, selama event yang disiarkannya tetap bertipe ViewEvent, saya nggak perlu mengubah Controller.

[js] package simplemvc.view {

import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.text.TextField;
import simplemvc.events.ViewEvent;
import simplemvc.model.Model;

/**
* …
* @author Anggie Bratadinata
*/
public class MainView extends MovieClip {

//timeline objects
public var hero:MovieClip;
public var board:MovieClip;
public var leftBtn:ArrowButton;
public var rightBtn:ArrowButton;
public var upBtn:ArrowButton;
public var downBtn:ArrowButton;
public var positionTxt:TextField;

private var _model:Model = Model.getInstance();

//———————————————————– INIT

public function MainView() {

initButtonListener();

_model.addEventListener(Event.CHANGE, modelListener);

}

private function initButtonListener():void {
var i:int = 0;
while (i < numChildren) {
if (this.getChildAt(i) is ArrowButton) {
this.getChildAt(i).addEventListener(MouseEvent.CLICK, buttonListener);
}
i++;
}

}

private function buttonListener(e:MouseEvent):void {

switch(e.currentTarget) {
case leftBtn:
dispatchEvent(new ViewEvent(ViewEvent.MOVE_LEFT));
break;
case rightBtn:
dispatchEvent(new ViewEvent(ViewEvent.MOVE_RIGHT));
break;
case upBtn:
dispatchEvent(new ViewEvent(ViewEvent.MOVE_UP));
break;
case downBtn:
dispatchEvent(new ViewEvent(ViewEvent.MOVE_DOWN));
break;
}
}

private function modelListener(e:Event):void {

hero.x = _model.heroPos.x;
hero.y = _model.heroPos.y;

positionTxt.text = _model.heroPos.toString();

}

}

}

[/js] [js] package simplemvc.events {
import flash.events.Event;

/**
* ...
* @author Anggie Bratadinata
*/
public class ViewEvent extends Event {

public static const MOVE_LEFT:String = "moveLeft";
public static const MOVE_RIGHT:String = "moveRight";
public static const MOVE_UP:String = "moveUp";
public static const MOVE_DOWN:String = "moveDown";

public function ViewEvent(type:String) {
super(type, true);
}

override public function clone():Event {
return new ViewEvent(type);
}

override public function toString():String {
return formatToString("ViewEvent", "type", "bubbles", "cancelable", "eventPhase");
}

}

}

[/js]

Model

Model adalah bagian dimana data berada. Untuk data yang bersifat global, kita bisa mengimplementasikan Singleton pattern. Singleton class hanya bisa diinstansiasi satu kali selama aplikasi berjalan. Umumnya, referensi ke instance dari Singleton diakses dengan memanggil static method getInstance().

Kalo kita bekerja bersama developer lain, kita bisa mencegah mereka menginstansiasi/membuat objek Singleton dengan menggunakan inner class yaitu class yang didefinisikan diluar package tetapi masih di dalam file yang sama.

Model saya gunakan untuk menyimpan data berupa posisi kotak kuning. Pada saat data berubah, Model akan men-dispatch Event.CHANGE yang didengarkan oleh MainView yang kemudian meng-update posisi kotak kuning.

[js] package simplemvc.model {

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Point;

/**
* …
* @author Anggie Bratadinata
*/
public class Model extends EventDispatcher {

public var heroHeight:Number = 0;
public var heroWidth:Number = 0;
//———————————————————– HERO VELOCITY

public var vx:Number = 10;
public var vy:Number = 10;

//———————————————————– HERO POSITION

private var _heroPos:Point = new Point();

public function get heroPos():Point { return _heroPos; }

public function set heroPos(value:Point):void {

if (value.x >= heroBounds.xMin && value.x <= heroBounds.xMax &&
value.y >= heroBounds.yMin && value.y <= heroBounds.yMax ) {

_heroPos = value;
dispatchEvent(new Event(Event.CHANGE));

}
}

public function moveDown():void {
heroPos = new Point(heroPos.x, heroPos.y + vy);
}

public function moveUp():void {
heroPos = new Point(heroPos.x, heroPos.y - vy);
}

public function moveLeft():void {
heroPos = new Point(heroPos.x - vx, heroPos.y);
}

public function moveRight():void {
heroPos = new Point(heroPos.x + vx, heroPos.y );
}

//----------------------------------------------------------- HERO BOUNDS

private var _heroBounds:Object = {};

public function get heroBounds():Object { return _heroBounds; }

public function set heroBounds(value:Object):void {
//trace(value);
_heroBounds.xMin = value.x;
_heroBounds.xMax = value.x + value.width - heroWidth;
_heroBounds.yMin = value.y;
_heroBounds.yMax = value.y + value.height - heroHeight;

}

//----------------------------------------------------------- INIT
private static var _instance:Model;
public function Model(enf:SingletonEnforcer) {}
public static function getInstance():Model {
if (_instance == null) _instance = new Model(new SingletonEnforcer());
return _instance;
}

}

}
//INNER CLASS
class SingletonEnforcer { };

[/js]

Controller

Controller mendengarkan ViewEvent yang disiarkan oleh MainView dan meng-update Model berdasarkan event tersebut.

[js] package simplemvc.controller {
import flash.events.Event;
import flash.geom.Point;
import simplemvc.model.Model;
import simplemvc.view.*;
import simplemvc.events.*;

/**
* …
* @author Anggie Bratadinata
*/
public class Controller {

private var _model:Model = Model.getInstance();
private var _view:MainView;

//———————————————————– INIT

public function Controller(view:MainView) {

_view = view;

_model.heroHeight = _view.hero.height;
_model.heroWidth = _view.hero.width;
_model.heroBounds = _view.board.getBounds(_view);

_view.addEventListener(ViewEvent.MOVE_DOWN, viewListener);
_view.addEventListener(ViewEvent.MOVE_UP, viewListener);
_view.addEventListener(ViewEvent.MOVE_LEFT, viewListener);
_view.addEventListener(ViewEvent.MOVE_RIGHT, viewListener);

}

public function startUp():void {
_model.heroPos = new Point(0, 0);
}

private function viewListener(e:ViewEvent):void {

switch(e.type) {
case ViewEvent.MOVE_DOWN:
_model.moveDown();
break;
case ViewEvent.MOVE_LEFT:
_model.moveLeft();
break;
case ViewEvent.MOVE_RIGHT:
_model.moveRight();
break;
case ViewEvent.MOVE_UP:
_model.moveUp();
break;
}
}

}

}

[/js]

Document/Main Class

[js] package {

import flash.display.MovieClip;
import simplemvc.controller.Controller;
import simplemvc.view.MainView;

/**
* …
* @author Anggie Bratadinata
*/
public class Main extends MovieClip {

public var mainView:MainView;
public var controller:Controller;

public function Main() {
controller = new Controller(mainView);
controller.startUp();
}

}

}

[/js]

Seperti Anda lihat, menulis kode dengan struktur MVC nggak terlalu sulit, hanya sedikit ngerepotin. :-)

Download

simple-mvc-src.zip

Also in this category ...

    no matches