Structure Oriented Programming VS Protocol Oriented Programming in Swift
In this article we will look at the main aspects of structure-oriented programming: how to write code according to the SOLID principles without using protocols and without losing abstraction.
The Structure Oriented Programming is a paradigm based on the fact that any protocol can be replaced by a structure.
The advantages of this approach is performance.
Since the structure-oriented approach is based on structures that use static dispatch, the dispatch speed will be significantly different from protocols with dynamic dispatch in the protocol-oriented approach.
1. Replacing a protocol with an equivalent structure
Let’s start by trying to replace the protocol with an equivalent structure.
In a protocol-oriented approach, the protocol itself allows abstraction from the implementation.
In the structure-oriented approach, generics and closures help to provide abstraction.
Consider, as an example, the composition of a protocol and a class, in which the protocol provides the square of a number for any object that will conform to this protocol:
// Abstraction
protocol RegularProtocol {
var sqrValue: Int { get }
}
// Object
class RegularClass {
let value: Int
init(value: Int) {
self.value = value
}
}
// Realization
extension RegularClass: RegularProtocol {
var sqrValue: Int {
value * value
}
}
let onbject = RegularClass(value: 25)
print(onbject.sqrValue)
Thus, the extension to the protocol adopt RegularProtocol
.
For a structure-oriented approach, the above would look like this:
// Abstraction
struct RegularStruct<AdoptedObject> {
var sqrValue: (AdoptedObject) -> Int
init(sqrValue: @escaping (AdoptedObject) -> Int) {
self.sqrValue = sqrValue
}
}
// Object
class RegularClass {
let value: Int
init(value: Int) {
self.value = value
}
}
// Realization
extension RegularStruct where AdoptedObject == RegularClass {
init() {
sqrValue = { object in object.value * object.value }
}
}
let onbject = RegularStruct<RegularClass>()
print(onbject.sqrValue(.init(value: 25)))
2. Covering all kinds of cases
To understand how to use structure-oriented approach in practice, let’s consider the most likely cases of using the protocol:
- Computed property
- Property with getter and setter
- Static property
- Regular method
- Static function
- Function with associated value
- Parent protocol function when inheriting protocols
For protocol-oriented approach all these cases are presented below:
protocol ProtocolExample: ParentProtocolExpample {
// 1
var getVariable: Int { get }
// 2
var getSetVariable: Int { get set }
// 3
static var staticVariable: Int { get }
// 4
func regularFunction(value: Int) -> Bool
// 5
static func staticFunction(value: Int) -> Bool
// 6
associatedtype Value
func assosiatedFunction(value: Value) -> Bool
}
protocol ParentProtocolExample {
// 7
func inheritedFunction() -> String
}
An equivalent composition for a structure-oriented approach looks like this:
struct StructExample<AdoptedObject, Value> {
// 1
var getVariable: (_ object: AdoptedObject) -> Int
// 2
var setVariable: (_ object: AdoptedObject, Int) -> Void
// 3
var staticVariable: () -> Int
// 4
var regularFunction: (_ object: AdoptedObject, _ value: Int) -> Bool
// 5
var staticFunction: (_ value: Int) -> Bool
// 6
var assosiatedFunction: (_ object: AdoptedObject, _ value: Value) -> Bool
// 7
var parentStruct: ParentStructExample<AdoptedObject>
var inheritedFunction: () -> String
}
struct ParentStructExample<AdoptedObject> {
var inheritedFunction: () -> String
}
3. Performance benefits
The effect of the implementation was tested with XCTest and the Optimization Level flags — Fastest, Smallest [-Os] and showed that dispatching is 3–4 times faster for the structure-oriented approach.
Protocol-oriented approach:
Structure-oriented approach
Don’t hesitate to contact me on Twitter if you have any questions. Also, you can always buy me a coffee.