Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to secure same inheritance hierarchies e.g. for MVVM

I´ve got a base ViewController and a base ViewModel. The base ViewModel is used by the base ViewController. Also, I´ve got 2 subclasses of ViewControllers and 2 subclasses of ViewModels that need to be used together.

Example:

class BaseViewModel {
    func somethingBasic() {}
}

class ConcreteViewModel1: BaseViewModel {
    func somethingConcrete1() {}
}

class ConcreteViewModel2: BaseViewModel {
    func somethingConcrete2() {}
}

class BaseViewController {
    let viewModel: BaseViewModel

    init(with viewModel: BaseViewModel) {
        self.viewModel = viewModel
    }
}

class ConcreteViewController1: BaseViewController {
    init(with viewModel: ConcreteViewModel1) {
        super.init(with: viewModel)
    }

    func useViewModel() {
        viewModel.somethingBasic()
        viewModel.somethingConcrete1() //this does not work
    }
}

class ConcreteViewController2: BaseViewController {
    init(with viewModel: ConcreteViewModel2) {
        super.init(with: viewModel)
    }

    func useViewModel() {
        viewModel.somethingBasic()
        viewModel.somethingConcrete2() //this does not work
    }
}

The question is: what is the preferred solution to make viewmodel.somethingConcrete1() and viewmodel.somethingConcrete2() work?

like image 423
stk Avatar asked Oct 29 '25 14:10

stk


2 Answers

Try using Generics for this.

Create init in BaseViewController accepting a generic parameter T constrained to type BaseViewModel, i.e.

class BaseViewController<T: BaseViewModel> {
    let viewModel: T

    init(with viewModel: T) {
        self.viewModel = viewModel
    }
}

Now inherit ConcreteViewController1 and ConcreteViewController2 from BaseViewController giving the specific type for generic parameter T, i.e.

class ConcreteViewController1: BaseViewController<ConcreteViewModel1> {
    func useViewModel() {
        viewModel.somethingBasic()
        viewModel.somethingConcrete1()
    }
}

class ConcreteViewController2: BaseViewController<ConcreteViewModel2> {
    func useViewModel() {
        viewModel.somethingBasic()
        viewModel.somethingConcrete2()
    }
}
like image 154
PGDev Avatar answered Oct 31 '25 03:10

PGDev


I discussed this with a few other colleagues, and we came around with this solution, based on Composition instead of inheritance:

class BaseViewModel {
    func somethingBasic() {}
}

class ConcreteViewModel1 {
    private let baseViewModel = BaseViewModel()
    func somethingConcrete1() {}

    func somethingBasic() {
        baseViewModel.somethingBasic()
    }
}

class ConcreteViewModel2 {
    private let baseViewModel = BaseViewModel()
    func somethingConcrete2() {}

    func somethingBasic() {
        baseViewModel.somethingBasic()
    }
}

class BaseViewController {}

class ConcreteViewController1 {
    private let base = BaseViewController()
    private let viewModel: ConcreteViewModel1

    init(with viewModel: ConcreteViewModel1) {
        self.viewModel = viewModel
    }

    func useViewModel() {
        viewModel.somethingBasic()
        viewModel.somethingConcrete1()
    }
}

class ConcreteViewController2: BaseViewController {
    private let base = BaseViewController()
    private let viewModel: ConcreteViewModel2

    init(with viewModel: ConcreteViewModel2) {
        self.viewModel = viewModel
    }

    func useViewModel() {
        viewModel.somethingBasic()
        viewModel.somethingConcrete2()
    }
}

With that solution, you get the type safety, you avoid Generics and you don´t need to cast anywhere.

like image 28
stk Avatar answered Oct 31 '25 03:10

stk