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 paradigm is based on the fact that a structure can replace any protocol.
The advantage of this approach is performance. Since the structure-oriented approach is based on structures that use static dispatch, the dispatch speed will significantly differ from protocols with dynamic dispatch in the protocol-oriented approach.
Let’s start by trying to replace the protocol with an equivalent structure.
In the 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)))
To understand how to use the 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 an associated value
Parent protocol function when inheriting protocols
For the 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
}
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.
The Protocol-oriented approach:
The Structure-oriented approach:
Don’t hesitate to contact me on