You are currently viewing Mutable/Immutable/View pattern example in Kotlin

Mutable/Immutable/View pattern example in Kotlin

I’ve recently learned about this pattern from this article. I’ve found it extremely useful for my own personal projects because I don’t have to create separate immutable/mutable classes. The read logic is shared amongst immutable/mutable classes so I don’t need to copy and paste them.

I’ve also extended it to also provide a

View

of the data. Which is basically a read only wrapper around mutable data. Here’s an example:


package models import java.util.* /** * Represents the discard pile in Mahjong. */ sealed class DiscardPile(protected val tiles: Stack<Tile>) { init { // Push an initial tile so that there's always a tile. tiles.push(Tile.NONE) } fun peekRecentlyDiscardedTile(): Tile { val discardedTile = tiles.peek()!! require(discardedTile != Tile.NONE) return discardedTile } fun getTiles(): List<Tile> { return tiles.filter { it != Tile.NONE }.toList() } fun makeString(): String { return tiles.filter { it != Tile.NONE }.windowed(15, step = 15, partialWindows = true) .joinToString(separator = "n") { it.joinToString() } } fun print() { println(makeString()) } protected fun tilesCopy(): Stack<Tile> { val stackCopy = Stack<Tile>() stackCopy.addAll(tiles) return stackCopy } class Mutable(tiles: Stack<Tile>? = null) : DiscardPile(tiles ?: Stack<Tile>()) { fun takeRecentlyDiscardedTile(): Tile { val discardedTile = tiles.pop()!! require(discardedTile != Tile.NONE) return discardedTile } fun accept(tile: Tile) { require(tile != Tile.NONE) require(!tile.isFlowerTile()) tiles.push(tile) } fun immutable(): Immutable { return Immutable(tilesCopy()) } fun view(): View { return View(tiles) } } class Immutable(tiles: Stack<Tile>? = null) : DiscardPile(tiles ?: Stack<Tile>()) { fun mutable(): Mutable { return Mutable(tilesCopy()) } } class View(tiles: Stack<Tile>? = null) : DiscardPile(tiles ?: Stack<Tile>()) { fun mutable(): Mutable { return Mutable(tiles) } } } fun main() { val discardPileMutable = DiscardPile.Mutable() discardPileMutable.accept(Tile.CIRCLE.ONE) val discardPileImmutable = discardPileMutable.immutable() val discardPileView = discardPileMutable.view() println("First calls") discardPileMutable.print() discardPileImmutable.print() discardPileView.print() discardPileMutable.accept(Tile.CIRCLE.TWO) println("Second calls") discardPileMutable.print() discardPileImmutable.print() discardPileView.print() }

Output:


First calls Ci1 Ci1 Ci1 Second calls Ci1, Ci2 Ci1 Ci1, Ci2 Process finished with exit code 0

Notes:

  • The caller doesn’t have access to the
    tiles

    field since it’s protected.

  • Methods that mutate the underlying data are only found under the
    Mutable

    class.

  • There are methods for converting between Immutable <-> Mutable <-> View classes.
  • The Immutable class is a deep copy of the original data. So changes to the original data doesn’t affect this class.

I would like to give props/credit to the author of that article for helping me learn this pattern. I hope y’all find it useful as well.

submitted by /u/IllTryToReadComments
[link] [comments]