Splitting up Swift types.
Bài đăng này đã không được cập nhật trong 5 năm
- 
Một ứng dụng khác nhau có các tính năng và hệ thống khác nhau nên được tách biệt rõ ràng về mặt chức năng và phạm vi ảnh hưởng của chúng là điều là điều rất được quan tâm trong công việc phát triển phần mềm. Vì vậy có nhiều cấu trúc cũng như kỹ thuật + nguyên tắc đã được ra mắt trong những năm qua giúp chúng ta có thể viết code dễ hơn trong Swift và nhiều ngôn ngữ khác. 
- 
Với bất kỳ loại cấu trúc nào chúng ta chọn để áp dụng trong dự án cụ thể nào đó chúng ta cần đảm bảo rằng mỗi type có một bộ trách nhiệm rõ ràng và duy nhất . Điều này đặc biệt trở nên khó khăn khi dự án tiếp tục được phát triển cũng như thêm nhiều tính năng mới. 
- 
Chúng ta hãy xem một vài kỹ thuật có thể giúp chúng ta thực hiện điều đó bằng cách phân tách các type một khi trách nhiệm của chúng đã vượt ra ngoài phạm vi cho phép. 
1/ States and scopes:
- Một source code trở nên phức tạp khi một type duy nhất cần xử lý có nhiều ảnh hưởngscopecũng như nhiều trạng tháistatekhác nhau. Ví dụ, chúng ta làm việc trênnetworking layercủa ứng dụng và chúng tai đã triển khai toàn bộclassđó trong mộtclassduy nhất làNetworkController:
class NetworkController {
    typealias Handler = (Result<Data, NetworkError>) -> Void
    var accessToken: AccessToken?
    
    ...
    func request(_ endpoint: Endpoint,
                 then handler: @escaping Handler) {
        var request = URLRequest(url: endpoint.url)
        if let token = accessToken {
            request.addValue("Bearer \(token)",
                forHTTPHeaderField: "Authorization"
            )
        }
        // Perform the request
        ...
    }
}
- 
Mặc dù việc triển khai toàn bộ 1 tính năng hoặc hệ thống trong một classkhông là điều xấu, nhưng trong trường hợp này chúng ta có 1source codekhó hiểu. Vì chúng ta sử dụng cùng một API để yêu cầu cảendpointđầu và cuối công khai cũng như những thời điểm yêu cầu xác thực.
- 
Sẽ dễ dàng hơn nếu chúng ta có thể sử dụng hệ thống type Swift để ngăn chặn mọi endpointyêu cầu xác thực được gọi mà không có mã thông báo truy cập hợp lệ. Bằng cách đó, chúng ta sẽ có thể xác thực mã mạng của mình kỹ lưỡng hơn nhiều vàocomplie time.
- 
Để thực hiện điều đó, hãy di chuyển tất cả các code liên quan đến authenticationvà mã thông báo truy cập từNetworkContodervà vào một khởi tạo mới củaclassđó, chúng tôi đặt tên làAuthenticatedNetworkContoder. Bộcontrollercho phép chúng ta thực hiện cácnetwork calldựa trênendpoint:
class AuthenticatedNetworkController {
    typealias Handler = (Result<Data, NetworkError>) -> Void
    private var tokens: NetworkTokens
    ...
    init(tokens: NetworkTokens, ...) {
        self.tokens = tokens
        ...
    }
    func request(_ endpoint: AuthenticatedEndpoint,
                 then handler: @escaping Handler) {
        refreshTokensIfNeeded { tokens in
            var request = URLRequest(url: endpoint.url)
            request.addValue("Bearer \(tokens.access)",
                forHTTPHeaderField: "Authorization"
            )
            
            // Perform the request
            ...
        }
    }
}
- 
Chúng ta cũng đã cung cấp cho network controllerloạiend pointchuyên dụng của riêng mìnhAuthenticatedEndpoint. Điều đó cũng chỉ rõ ràng các định nghĩaendpointcủa chúng ta, do đó1endpointyêu cầu xác thực đã vô tình được chuyển đếnNetworkContodertrước đó của chúng ta.
- 
Vì typeđó không còn phải xử lý bất kỳ yêu cầu xác thực nào nên chúng ta có thể đơn giản hóa rất nhiều và đổi tên nó (và loạiend pointcủa nó) mô tả tốt hơn vai trò mới của nó trongnetwork layercủa chúng ta :
class NonAuthenticatedNetworkController {
    typealias Handler = (Result<Data, NetworkError>) -> Void
    
    ...
    func request(_ endpoint: NonAuthenticatedEndpoint,
                 then handler: @escaping Handler) {
        var request = URLRequest(url: endpoint.url)
        ...
    }
}```
- Chúng ta cần một kiến trúc và độ rõ ràng của API thì cũng phải chấp nhận một số lượng code bị trùng lặp. Trong trường hợp này cả 2 `network controller` của chúng ta cần tạo các phiên bản `URLRequest` và thực hiện chúng, cũng như xử lý các tác vụ như lưu trữ và các hoạt động liên quan đến mạng khác. 
```swift
typealias NetworkResultHandler<T> = (Result<T, NetworkError>) -> Void
- Chúng ta có thể bắt đầu chuyển phần  triển khai networkcơ bản ra khỏicontrollervà thành cáctypenhỏ hơn, chuyên dụng hơn . Ví dụ: chúng ta có thể tạo một loạiNetworkRequestPerformerriêng mà cả 2controllercủa chúng ta có thể sử dụng để thực hiện các yêu cầu củacontrollertrong khi vẫn giữ cácAPIcấp cao nhất hoàn toàn tách biệt và an toàn vềtype:
private struct NetworkRequestPerformer {
    var url: URL
    var accessToken: AccessToken?
    var cache: Cache<URL, Data>
    func perform(then handler: @escaping NetworkResultHandler<Data>) {
        if let data = cache.data(forKey: url) {
            return handler(.success(data))
        }
                        
        var request = URLRequest(url: url)
        // This if-statement is no longer a problem, since it's now
        // hidden behind a type-safe abstraction that prevents
        // accidential misuse.
        if let token = accessToken {
            request.addValue("Bearer \(token)",
                forHTTPHeaderField: "Authorization"
            )
        }
        
        ...
    }
}
-Bây giờ đây chúng ta có thể cho phép cả hai network controller của mình chỉ tập trung vào việc cung cấp API an toàn loại để thực hiện các yêu cầu  trong khi các triển khai cơ bản của chúng đang được giữ đồng bộ thông qua các loại tiện ích:
class AuthenticatedNetworkController {
    ...
    func request(
        _ endpoint: AuthenticatedEndpoint,
        then handler: @escaping NetworkResultHandler<Data>
    ) {
        refreshTokensIfNeeded { [cache] tokens in
            let performer = NetworkRequestPerformer(
                url: endpoint.url,
                accessToken: tokens.access,
                cache: cache
            )
            performer.perform(then: handler)
        }
    }
}
2/ Loading versus managing objects:
- 
Tiếp theo, hãy xem xét một loại tình huống khác có thể làm cho một số phần nhất định của source codephức tạp hơn mức cần thiết, khi cùng loại chịu trách nhiệm cho cả việcloadingvàmanaging1objectnhất định. Một ví dụ rất phổ biến đó là khi mọi thứ liên quan đếnsessioncủa người dùng đã được triển khai trong mộttypeduy nhất - chẳng hạn nhưUserManager.
- 
Ví dụ: ở đây một typenhư vậy chịu trách nhiệm cho cả người dùng đăng nhập vào và thoát khỏi ứng dụng , giữ choUserhiện đang đăng nhập đồng bộ hóa với máy chủ của chúng ta:
class UserManager {
    private(set) var user: User?
    
    ...
    func logIn(
        with credentials: LoginCredentials,
        then handler: @escaping NetworkResultHandler<User>
    ) {
        ...
    }
    func sync(then handler: @escaping NetworkResultHandler<User>) {
        ...
    }
    func logOut(then handler: @escaping NetworkResultHandler<Void>) {
        ...
    }
}
- Điều đầu tiên chúng ta sẽ làm là trích xuất tất cả các codeliên quan đến việc tải các phiên bảnUSertừUserManagervà vào mộttypemới riêng biệt. Chúng ta sẽ gọi nó làUserLoadervà sử dụngAuthenticatedNetworkControllcủa chúng ta từ trước để yêu cầuendpointcủauser:
struct UserLoader {
    var networkController: AuthenticatedNetworkController
    func loadUser(
        withID id: User.ID,
        then handler: @escaping NetworkResultHandler<User>
    ) {
        networkController.request(.user(withID: id)) { result in
            // Decode the network result into a User instance,
            // then call the passed handler with the end result.
            ...
        }
    }
}
- Bằng cách phân tách UserManagercủa chúng ta thành các khối nhỏ hơn chúng ta có thể cho phép nhiều chức năng của chúng ta được triển khai dưới dạng cấu trúc khôngstatevì cáctypeđó sẽ đơn giản thực hiện các tác vụ thay cho các đối tượng khác (giống nhưNetworkRequestPerformercủa chúng ta trước đó).
struct LoginPerformer {
    var networking: NonAuthenticatedNetworkController
    func login(
        using credentials: LoginCredentials,
        then handler: @escaping NetworkResultHandler<NetworkTokens>
    ) {
        // Send the passed credentials to our server's login
        // endpoint, and then call the passed completion handler
        // with the tokens that were returned.
        ...
    }
}
- Ưu điểm của đoạn code trên là giờ đây chúng ta có thể sử dụng 1 trong 2 typemới của mình bất cứ khi nào chúng ta cần thực hiện nhiệm vụ cụ thể củatypeđó thay vì luôn phải sử dụng cùng một loạiUserManagerbất kể chúng ta đăng nhập, đăng xuất hay đơn giản cập nhậtUserhiện tại.
class UserManager {
    private(set) var user: User
    private let loader: UserLoader
    init(user: User, loader: UserLoader) {
        self.user = user
        self.loader = loader
    }
    func sync(then handler: @escaping NetworkResultHandler<User>) {
        loader.loadUser(withID: user.id) { [weak self] result in
            if let user = try? result.get() {
                self?.user = user
            }
            handler(result)
        }
    }
}
- Công cụ tái cấu trúc ở trên không chỉ cho phép chúng ta loại bỏ 1 tùy chọn không cần thiết  mà còn cho phép chúng tai loại bỏ rất nhiều câu lệnh không rõ ràng nếu và bảo vệ khỏi bất kỳ code nào phụ thuộc vào Userđang đăng nhập cung cấp cho chúng ta mức độ linh hoạt cao hơn vì giờ đây chúng ta có thể chọn mức độ trừu tượng liên quan đếnUsermà chúng ta muốn làm việc trong mỗi tính năng mới mà chúng ta sẽ xây dựng.
All rights reserved
 
  
 