Noodlecode parody of spaghetti code

Month: November 2012

OOP the right way

Most of tutorials on object oriented programming tells the reader about how to do it rather than why we have to write it that way. This post attempts to explain why we write object oriented code the way it is.

Why object oriented? Object oriented is a way to write an application in components to reduce code repetition, enhance maintainability (able to unit test) & to let developer to focus on specific parts of app (separation of concern). Therefore, an app must be structured into components (component-based application).

In explaining class & objects in oop, it’s better to use real world example. Here I’ll take cache component of a web application. The basic functionality of cache component is to store app level data into temporary storage & retrieve it quickly. Now we can assume the basic class of a cache component is like this:

class Cache {
	public function get($key) {}
	public function set($key, $data, $expired) {}
	public function delete($key) {}
}

Notice we’re using public method. Public methods are used to expose functionality of a component for other components to use. This is the basic of designing an API, we’re defining how to use a component, what input it receives & what the output it’s expected to produce

class Cache {
	/**
	 * @param string $key
	 * @return array|false
	 */
	public function get($key) {}
	/**
	 * @param string $key
	 * @param mixed $data
	 * @param string|int $expired
	 */
	public function set($key, $data, $expired) {}

	// ...
}

How about the implementation? We know that cache component can use many types of backend, such as file based, SQLite, Memcached, Redis etc. Here is where inheritance is useful. Inheritance is used to either implement methods to a specific condition or to change the way data is processed (override).

class Cache {
	// ... public methods ...

	protected function purge() {}
	protected function getExpirationTime() {}
	protected function formatInputData() {}
	protected function formatOutputData() {}
}

Here we are using protected method. Protected is used on methods that are not going to be used by other components – these methods are going to be override by child class & used within the class itself. Let’s take a look at the example of child class implementations:

// using sqlite backend
class SQLiteCache extends Cache {
	private $connection;
	public function __construct() {
		parent::__construct();
		$this->connect();
	}
	private function connect() {
		// connect to database
		$this->connection = new PDO('sqlite::memory:');
	}
}

// using memcached backend
class MemcachedCache extends Cache {}

// using file-based backend
class FileCache extends Cache {
	// expose specific hidden methods to let extended class
	// to manipulate the data based on its backend
	protected function formatInputData() {
		if (!is_string($this->data)) {
			$this->data = serialize($this->data);
		}
	}
}

In SQLiteCache class, it has its own connect() method, that’s going to be used in this class only. That’s why it’s declared private. We may have connect() method in MemcachedCache to connect to memcached server, however the implementation is totally different from SQLiteCache, so we may write those methods separately without needing a parent class. Only create a parent class to combine child classes that has similar code (reduce code repetition).

In FileCache, we’re implementing formatInputData() because we couldn’t store PHP variable other than strings into file, so we serialize() the vars. This is the appropriate way to implement hook pattern (like in WordPress). Since this methods is declared in our base Cache class, this method can be called before storing the data into cache store, with different type of backend may already manipulate the data format to suite the backend requirement.

Let’s say this code is packaged in a module released by other developer and we’re not satisfied with the MemcachedCache implementation, we can extends that class and write our own implementation. This way, we’re not disturbing the module code & it’s important so that we don’t have to hack the module code again every time we want to update it.

class BetterMemcachedCache extends MemcachedCache {
	protected function getExpirationTime() {
		// return time-to-live instead of expiration timestamp
		return strtotime($this->expired, 0);
	}
}

Summary: Write OOP code in components, public methods are the API of the components, protected methods are for specific implementation & private methods are for usage within that class only. In general, we may have a lot of protected methods (to achieve hook pattern) and a few private methods (only to group repetitious code).

Designing web page for Android webview

Android screen built-in resolutions (based on API 8-16):

Skin Resolution (w x h pixel) Orientation
QVGA 240 x 320 Portrait
WQVGA400 240 x 400 Portrait
WQVGA432 240 x 432 Portrait
HVGA 320 x 480 Portrait
WVGA800 480 x 800 Portrait
WVGA854 480 x 854 Portrait
WSVGA 1024 x 600 Landscape
WXGA720 1280 x 720 Landscape
WXGA, WXGA800, WXGA800-7in 1280 x 800 Landscape

Android screen density

Density DPI / Pixel ratio Skin
ldpi 120 / 0.75 QVGA, WQVGA400, WQVGA432
mdpi 160 / 1.0 HVGA, WSVGA, WXGA
tvdpi 213 / 1.33125 WVGA800-7in
hdpi 240 / 1.5 WVGA800, WVGA854
xhdpi 320 / 2.0 WXGA720, WXGA800

Android webview viewport size (100% width and height)

Skin Viewport (w x h pixel, portrait)
QVGA 320 x 427
WQVGA400 320 x 533
WQVGA432 320 x 576
HVGA 320 x 480
WVGA800 320 x 533
WVGA854 320 x 569
WSVGA 600 x 1024
WXGA720 360 x 598
WXGA 800 x 1280
WXGA800 400 x 598
WXGA800-7in 601 x 962

In the HTML page, make sure we lock the webpage from being resize/zoom

<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />

To design responsive web page, pay attention to webview viewport size in writing the css media query. From the viewport size table above, if we’re designing for portrait orientation only, we have a set of widths: 320, 360, 400, 600, 800 (601 can use 600 width styles). So our css code must be organized like this:

/* width: 800 */
.container { width: 800px; }

/* width: 600 */
@media (max-width: 799px) {
    .container { width: 600px; }
}

/* width: 400 */
@media (max-width: 599px) {
    .container { width: 400px; }
}

/* width: 360 */
@media (max-width: 399px) {
    .container { width: 360px; }
}

/* width: 320 */
@media (max-width: 359px) {
    .container { width: 320px; }
}

Note: Since landscape width got more, choose widths that can be grouped together, e.g: 420, 480, 530, 960, 1024, 1280

In handling different screen density using css, it’s recommended to display images as background of html elements.
HTML:

<i class="icon ok"></i>

CSS:

.icon {
    width: 50px;
    height: 50px;
    display: block;
    background-size: 50px 50px;
}
.icon.ok {
    background-image: url(img/mdpi/icon_ok.png);
}

That’s for the base density (mdpi). For other densities:

/* density: xhdpi */
@media screen and (-webkit-device-pixel-ratio: 2.0) {
    .icon.ok { background-image: url(img/xhdpi/icon_ok.png); }
}

/* density: hdpi */
@media screen and (-webkit-device-pixel-ratio: 1.5) {
    .icon.ok { background-image: url(img/hdpi/icon_ok.png); }
}

/* density: ldpi */
@media screen and (-webkit-device-pixel-ratio: 0.75) {
    .icon.ok { background-image: url(img/ldpi/icon_ok.png); }
}

Note: for tvdpi, it’ll use hdpi stylesheet & resize accordingly.

Image sizes. To know what image size you need, use pixel ratio to resize the image (base design is using 1.0 pixel ratio). Image DPI (dot per inch) is 72. E.g:

Image file Density / Pixel ratio Size
img/ldpi/icon_ok.png ldpi / 0.75 50 x 0.75 = 38px
img/mdpi/icon_ok.png mdpi / 1.0 50 x 1.0 = 50px
img/hdpi/icon_ok.png hdpi / 1.5 50 x 1.5 = 75px
img/xhdpi/icon_ok.png xhdpi / 2.0 50 x 2.0 = 100px

Copyright © 2017 Noodlecode