Reputation: 4692
I'm implementing a model:
ClientSummary
and ClientDetails
ClientDetails
struct has all properties of ClientSummary
struct + some extra propertiesinit(jsonDictionary: [String: Any])
inits
of ClientSummary
and ClientDetails
share big part of the codeThe most straightforward solution which came to my mind is just classic inheritance, but it doesn't work for value types.
I'm trying to solve that with protocols, but I can't implement those "shared inits". I was trying to move shared part of the init to the protocol extension but can't really make it. There are various errors.
Here is the test code.
protocol Client {
var name: String { get }
var age: Int { get }
var dateOfBirth: Date { get }
init?(jsonDictionary: [String: Any])
}
struct ClientSummary: Client {
let name: String
let age: Int
let dateOfBirth: Date
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
}
}
struct ClientDetails: Client {
let name: String
let age: Int
let dateOfBirth: Date
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
visitHistory = [Date(): "Test"]
}
}
extension Client {
// A lot of helper methods here
var stringDOB: String {
return formatter.string(from: dateOfBirth)
}
}
Upvotes: 2
Views: 134
Reputation: 2802
For structs you can use composition instead of relying on inheritance. Let's suppose you already have ClientSummary
struct defined with the Client
protocol:
protocol Client {
var name: String { get }
var age: Int { get }
var dateOfBirth: Date { get }
init?(jsonDictionary: [String: Any])
}
struct ClientSummary: Client {
let name: String
let age: Int
let dateOfBirth: Date
init?(jsonDictionary: [String: Any]) {
guard let name = jsonDictionary["name"] as? String else {
return nil
}
self.name = name
age = 1
dateOfBirth = Date()
}
}
Now to create ClientDetails
sharing ClientSummary
logic you can just create a ClientSummary
property in ClientDetails
. This way have the same initializer as ClientSummary
with your additional type specific logic and with use of dynamicMemberLookup
you can access ClientSummary
properties on ClientDetails
type:
@dynamicMemberLookup
struct ClientDetails {
var summary: ClientSummary
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
return nil
}
self.summary = summary
visitHistory = [Date(): "Test"]
}
subscript<T>(dynamicMember path: KeyPath<ClientSummary, T>) -> T {
return summary[keyPath: path]
}
subscript<T>(dynamicMember path: WritableKeyPath<ClientSummary, T>) -> T {
get {
return summary[keyPath: path]
}
set {
summary[keyPath: path] = newValue
}
}
subscript<T>(dynamicMember path: ReferenceWritableKeyPath<ClientSummary, T>) -> T {
get {
return summary[keyPath: path]
}
set {
summary[keyPath: path] = newValue
}
}
}
There is an extension which will work with shared functionality of those structs.
Now sharing code between ClientSummary
and ClientDetails
is tricky. By using dynamicMemberLookup
you will be able to access all the properties in ClientSummary
from ClientDetails
but methods from ClientSummary
can't be invoked this way. There is proposal to fulfill protocol requirements with dynamicMemberLookup
which should allow you to share methods between ClientSummary
and ClientDetails
for now you have to invoke ClientSummary
methods on ClientDetails
using the summary
property.
Upvotes: 0
Reputation: 299643
Inheritance is the wrong tool here. It doesn't make sense to say "details IS-A summary." Details are not a kind of summary. Step away from the structural question of whether they share a lot of methods, and focus on the essential question of whether one is a kind of the other. (Sometimes renaming things can make that true, but as long as they're "summary" and "detail" it doesn't make sense to inherit.)
What can make sense is to say that details HAS-A summary. Composition, not inheritance. So you wind up with something like:
struct ClientDetails {
let summary: ClientSummary
let visitHistory: [Date: String]?
init?(jsonDictionary: [String: Any]) {
guard let summary = ClientSummary(jsonDictionary: jsonDictionary) else {
return nil
}
self.summary = summary
visitHistory = [Date(): "Test"]
}
// You can add these if you need them, or to conform to Client if that's still useful.
var name: String { return summary.name }
var age: Int { return summary.age }
var dateOfBirth: Date { return summary.dateOfBirth }
}
Upvotes: 4
Reputation: 17060
I often wish that Swift had a built-in way to separate out parts of init methods. However, it can be done, admittedly somewhat awkwardly, with tuples, as below:
struct S {
let foo: String
let bar: Int
let baz: Bool
init() {
(self.foo, self.bar, self.baz) = S.sharedSetup()
}
static func sharedSetup() -> (String, Int, Bool) {
...
}
}
In your case, the sharedSetup() method can be moved to the protocol extension, or wherever it's convenient to have it.
Upvotes: 1