Tôi đã làm SOLID trong C # ở mức độ khá cao trong thời gian gần đây và tại một thời điểm nào đó tôi nhận ra rằng về cơ bản ngày nay tôi không làm nhiều việc khác ngoài việc soạn các hàm. Và sau khi tôi bắt đầu nhìn lại F # một lần nữa, tôi nhận ra rằng nó có lẽ sẽ là lựa chọn ngôn ngữ thích hợp hơn nhiều cho phần lớn những gì tôi đang làm bây giờ, vì vậy tôi muốn thử và chuyển một dự án C # trong thế giới thực sang F # như một bằng chứng về khái niệm. Tôi nghĩ rằng tôi có thể rút ra mã thực tế (theo một cách rất không thành ngữ), nhưng tôi không thể tưởng tượng một kiến trúc sẽ trông như thế nào cho phép tôi làm việc theo kiểu linh hoạt tương tự như trong C #.
Ý tôi là tôi có rất nhiều lớp và giao diện nhỏ mà tôi soạn bằng IoC container, và tôi cũng sử dụng nhiều mẫu như Decorator và Composite. Điều này dẫn đến (theo ý kiến của tôi) một kiến trúc tổng thể rất linh hoạt và có thể phát triển cho phép tôi dễ dàng thay thế hoặc mở rộng chức năng tại bất kỳ điểm nào của ứng dụng. Tùy thuộc vào mức độ lớn của thay đổi được yêu cầu, tôi có thể chỉ cần viết một triển khai mới của một giao diện, thay thế nó trong đăng ký IoC là xong. Ngay cả khi thay đổi lớn hơn, tôi có thể thay thế các phần của biểu đồ đối tượng trong khi phần còn lại của ứng dụng chỉ đơn giản là đứng như trước đây.
Bây giờ với F #, tôi không có các lớp và giao diện (tôi biết tôi có thể, nhưng tôi nghĩ đó là điều không cần thiết khi tôi muốn lập trình hàm thực tế), tôi không có hàm khởi tạo và tôi không có IoC hộp đựng. Tôi biết tôi có thể làm một cái gì đó giống như một mẫu Decorator bằng cách sử dụng các hàm bậc cao hơn, nhưng điều đó dường như không mang lại cho tôi sự linh hoạt và khả năng bảo trì giống như các lớp có chèn hàm tạo.
Hãy xem xét các loại C # sau:
public class Dings
{
public string Lol { get; set; }
public string Rofl { get; set; }
}
public interface IGetStuff
{
IEnumerable<Dings> For(Guid id);
}
public class AsdFilteringGetStuff : IGetStuff
{
private readonly IGetStuff _innerGetStuff;
public AsdFilteringGetStuff(IGetStuff innerGetStuff)
{
this._innerGetStuff = innerGetStuff;
}
public IEnumerable<Dings> For(Guid id)
{
return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
}
}
public class GeneratingGetStuff : IGetStuff
{
public IEnumerable<Dings> For(Guid id)
{
IEnumerable<Dings> dingse;
// somehow knows how to create correct dingse for the ID
return dingse;
}
}
Tôi sẽ nói với container IoC của tôi để giải quyết AsdFilteringGetStuff
cho IGetStuff
và GeneratingGetStuff
cho sự phụ thuộc của mình với giao diện đó. Bây giờ nếu tôi cần một bộ lọc khác hoặc xóa bộ lọc hoàn toàn, tôi có thể cần triển khai tương ứng IGetStuff
và sau đó chỉ cần thay đổi đăng ký IoC. Miễn là giao diện vẫn giữ nguyên, tôi không cần phải chạm vào những thứ bên trong ứng dụng. OCP và LSP, được kích hoạt bởi DIP.
Bây giờ tôi phải làm gì trong F #?
type Dings (lol, rofl) =
member x.Lol = lol
member x.Rofl = rofl
let GenerateDingse id =
// create list
let AsdFilteredDingse id =
GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")
Tôi thích mã này ít hơn bao nhiêu, nhưng tôi mất tính linh hoạt. Có, tôi có thể gọi AsdFilteredDingse
hoặc GenerateDingse
ở cùng một nơi, vì các loại đều giống nhau - nhưng làm cách nào để tôi quyết định gọi cái nào mà không cần mã hóa khó tại trang web cuộc gọi? Ngoài ra, mặc dù hai chức năng này có thể hoán đổi cho nhau, nhưng bây giờ tôi không thể thay thế chức năng máy phát điện bên trong AsdFilteredDingse
mà không thay đổi chức năng này. Điều này không tốt cho lắm.
Lần thử tiếp theo:
let GenerateDingse id =
// create list
let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
generator id |> List.filter (fun x -> x.Lol = "asd")
Bây giờ tôi có khả năng kết hợp bằng cách biến AsdFilteredDingse thành một hàm bậc cao hơn, nhưng hai hàm không thể hoán đổi cho nhau nữa. Suy nghĩ thứ hai, họ có lẽ không nên như vậy.
Tôi có thể làm gì khác? Tôi có thể bắt chước khái niệm "gốc sáng tác" từ C # SOLID của tôi trong tệp cuối cùng của dự án F #. Hầu hết các tệp chỉ là tập hợp các chức năng, sau đó tôi có một số loại "sổ đăng ký", thay thế vùng chứa IoC và cuối cùng có một chức năng mà tôi gọi để thực sự chạy ứng dụng và sử dụng các chức năng từ "sổ đăng ký". Trong "sổ đăng ký", tôi biết tôi cần một chức năng thuộc loại (Hướng dẫn -> Danh sách Dings), mà tôi sẽ gọi GetDingseForId
. Đây là cái tôi gọi, không bao giờ là các hàm riêng lẻ được định nghĩa trước đó.
Đối với người trang trí, định nghĩa sẽ là
let GetDingseForId id = AsdFilteredDingse GenerateDingse
Để xóa bộ lọc, tôi sẽ thay đổi bộ lọc đó thành
let GetDingseForId id = GenerateDingse
Nhược điểm (?) Của điều này là tất cả các hàm sử dụng các hàm khác hợp lý sẽ phải là các hàm bậc cao hơn và "sổ đăng ký" của tôi sẽ phải ánh xạ tất cả các hàm mà tôi sử dụng, bởi vì các hàm thực tế được xác định trước đó không thể gọi bất kỳ hàm nào các chức năng được xác định sau đó, đặc biệt không phải các chức năng từ "sổ đăng ký". Tôi cũng có thể gặp phải các vấn đề phụ thuộc vòng tròn với ánh xạ "sổ đăng ký".
co phải vai Điêu nay không co y nghia gi? Làm thế nào để bạn thực sự xây dựng một ứng dụng F # để có thể bảo trì và phát triển được (chưa kể có thể kiểm tra được)?
Composition
mô-đun, về cơ bản là một gốc cấu thành "DI người nghèo". Đó có phải là một cách khả thi để đi?