2013-08-27 37 views
12

Cho một ứng dụng ASP.NET MVC với các lớp sau:Khi nào cần sử dụng giao diện và khi nào cần sử dụng các chức năng bậc cao hơn?

  • UI (Lượt xem, CSS, Javascript, vv)
  • Controller
  • Dịch vụ (Có logic kinh doanh và truy cập dữ liệu)

Lý do không có lớp truy cập dữ liệu riêng biệt, là tôi đang sử dụng nhà cung cấp loại SQL.

(Mã sau có thể không hoạt động vì nó chỉ là bản nháp thô). Bây giờ hãy tưởng tượng một dịch vụ có tên UserService được xác định như sau:

module UserService = 
    let getAll memoize f = 
     memoize(fun _ -> f) 

    let tryGetByID id f memoize = 
     memoize(fun _ -> f id) 

    let add evict f name keyToEvict = 
     let result = f name 
     evict keyToEvict 
     result 

Và sau đó trong lớp điều khiển của tôi, tôi sẽ có mô-đun khác tên UserImpl hoặc nó có thể chỉ cũng được đặt tên UserMemCache:

module UserImpl = 
    let keyFor = MemCache.keyFor 
    let inline memoize args = 
     MemCache.keyForCurrent args 
     |> CacheHelpers.memoize0 MemCache.tryGet MemCache.store 

    let getAll = memoize [] |> UserService.getAll 
    let tryGetByID id = memoize [id] |> UserService.tryGetByID id 
    let add = 
     keyFor <@ getAll @> [id] 
     |> UserService.add MemCache.evict 

Các việc sử dụng điều này sẽ như sau:

type UserController() = 
    inherit Controller() 

    let ctx = dbSchema.GetDataContext() 

    member x.GetAll() = UserImpl.getAll ctx.Users 
    member x.UserNumberOne = UserImpl.tryGetByID ctx.Users 1 
    member x.UserNumberTwo = UserImpl.tryGetByID ctx.Users 2 
    member x.Add(name) = UserImpl.add ctx.Users name 

Sử dụng giao diện, chúng tôi sẽ thực hiện như sau:

type UserService(ICacheProvider cacheProvider, ITable<User> db) = 
    member x.GetAll() = 
     cacheProvider.memoize(fun _ -> db |> List.ofSeq) 

    member x.TryGetByID id = 
     cacheProvider.memoize(fun _ -> db |> Query.tryFirst <@ fun z -> z.ID = ID @>) 

    member x.Add name = 
     let result = db.Add name 
     cacheProvider.evict <@ x.GetAll() @> [] 
     result 

Và việc sử dụng sẽ là một cái gì đó như:

type UserController(ICacheProvider cacheProvider) = 
    inherit Controller() 

    let ctx = dbSchema.GetDataContext() 
    let userService = new UserService(cacheProvider, ctx.Users) 

    member x.GetAll() = userService.GetAll() 
    member x.UserNumberOne = userService.TryGetByID 1 
    member x.UserNumberTwo = userService.TryGetByID 2 

Rõ ràng việc thực hiện giao diện còn ít nhiều mã, nhưng nó không thực sự cảm thấy như mã chức năng nữa. Nếu tôi bắt đầu sử dụng giao diện trong suốt ứng dụng web của mình, khi nào tôi biết khi nào nên sử dụng các hàm bậc cao hơn thay thế? - nếu không tôi sẽ kết thúc bằng một giải pháp OOP cũ.

Vì vậy, trong ngắn hạn: Khi nào giao diện nên được sử dụng và khi nào nên sử dụng các hàm bậc cao hơn? - một số dòng đã được rút ra, hoặc tất cả sẽ là các loại và giao diện, trong đó vẻ đẹp của FP biến mất.

+1

Tôi đã viết một vài suy nghĩ về vấn đề này trên blog của tôi, cố gắng làm rõ mối quan hệ giữa các đối tượng và chức năng bậc cao trong số những thứ khác: http://bugsquash.blogspot.com/2013/08/objects-and-functional-programming.html –

Trả lời

10

Trên giao diện. Trước hết, tôi nghĩ bạn có thể thấy các giao diện giống như các cặp hàm được đặt tên. Nếu bạn có:

type ICacheProvider = 
    abstract Get : string -> option<obj> 
    abstract Set : string * obj -> unit 

thì đây là khá nhiều tương đương với việc có một cặp (hoặc một kỷ lục) các chức năng:

type CacheProvider = (string -> option<obj>) * (string * obj -> unit) 

Lợi ích của việc sử dụng giao diện là bạn hãy đưa ra kiểu một tên (bạn sẽ nhận được rằng với hồ sơ quá) và bạn rõ ràng hơn thể hiện ý định của bạn (các thành phần khác có thể thực hiện giao diện).

Tôi nghĩ rằng việc sử dụng giao diện là một ý tưởng hay nếu bạn có nhiều hơn 2 hàm thường được chuyển đến một số hàm khác cùng nhau - theo cách này, bạn tránh có quá nhiều tham số.

Mô-đun hoặc lớp học. Sự khác biệt thực sự trong mã của bạn là có sử dụng mô-đun với các hàm bậc cao hơn hay một lớp có giao diện làm đối số hàm tạo. F # là một ngôn ngữ đa mô hình kết hợp chức năng và phong cách OO, vì vậy tôi nghĩ rằng việc sử dụng các lớp theo cách này là hoàn toàn tốt đẹp.(Bạn vẫn có thể hưởng lợi từ kiểu chức năng khi xác định loại dữ liệu để đại diện cho tên miền, v.v.)

Một điều cần lưu ý là lập trình hàm là tất cả về thành phần. Điều này có thể không phải là hữu ích trong trường hợp này, nhưng tôi luôn thích viết code mà tôi có thể soạn thêm nhiều chức năng hơn chứ không phải là mã mà đòi hỏi tôi phải cung cấp một cái gì đó khi tôi muốn sử dụng nó.

Có lẽ bạn có thể viết nó để mã truy cập cơ sở dữ liệu của bạn không làm bộ nhớ đệm trực tiếp (điều này sẽ bao gồm tất cả các truy vấn cơ sở dữ liệu và logic tiền xử lý):

module UserService = 
    let getAll() = (...) 
    let tryGetByID id = (...) 
    let add name = (...) 

... và sau đó định nghĩa một kiểu mà kết thúc tốt đẹp này và cho biết thêm bộ nhớ đệm (và sau đó điều này sẽ được sử dụng bởi các loại chính của ứng dụng web - đó là khá giống với loại bạn định nghĩa trong ví dụ của bạn, nhưng bây giờ chúng tôi đang tách các truy cập cơ sở dữ liệu và ghi nhớ cách sử dụng một nhà cung cấp bộ nhớ cache) :

type UserService(cacheProvider:ICacheProvider) = 
    member x.GetAll() = cacheProvider.memoize UserSerivce.getAll() 
    member x.TryGetByID id = cacheProvider.memoize UserService.tryGetByID id 
    member x.Add name = cacheProvider.memoize UserService.add name 

Tóm tắt. Nhưng - Tôi nghĩ rằng cách tiếp cận của bạn sử dụng một lớp mà có ICacheProvider là hoàn toàn tốt đẹp - F # là khá tốt trong quá trình trộn chức năng và đối tượng theo phong cách định hướng. Ví dụ tôi đăng thực sự chỉ là một phần mở rộng có thể là có thể có ích trong các dự án lớn hơn (nếu bạn muốn sử dụng các khía cạnh chức năng và các khía cạnh khác nhau rõ ràng riêng biệt của các chức năng)

+0

Cũng khuôn khổ kiểm tra đơn vị có xu hướng chơi đẹp hơn nhiều với giao diện chế giễu thay vì chế giễu ví dụ tuples của chữ ký chức năng. –

Các vấn đề liên quan