2011-12-30 23 views
6

Tôi đã đọc về cách tiếp cận giao diện thông thạo của OO ở Java, JavaScriptScala và tôi thích giao diện của nó, nhưng đã phải vật lộn để xem cách điều chỉnh nó bằng phương pháp tiếp cận dựa trên kiểu/chức năng hơn trong Scala .Làm cách nào để kết hợp giao diện thông thạo với phong cách chức năng trong Scala?

Để cung cấp một ví dụ rất cụ thể về những gì tôi muốn nói: Tôi đã viết một ứng dụng API mà có thể được gọi như thế này:

val response = MyTargetApi.get("orders", 24) 

Giá trị trả về từ get() là một loại Tuple3 gọi RestfulResponse, theo quy định trong tôi package object:

// 1. Return code 
// 2. Response headers 
// 2. Response body (Option) 
type RestfulResponse = (Int, List[String], Option[String]) 

này hoạt động tốt - và tôi không thực sự muốn hy sinh sự đơn giản chức năng của một giá trị tuple trở lại - nhưng tôi muốn mở rộng thư viện với nhiều 'f luent' cuộc gọi phương pháp, có lẽ một cái gì đó như thế này:

val response = MyTargetApi.get("customers", 55).throwIfError() 
// Or perhaps: 
MyTargetApi.get("orders", 24).debugPrint(verbose=true) 

Làm thế nào tôi có thể kết hợp đơn giản chức năng của get() trả về một tuple gõ (hoặc tương tự) với khả năng bổ sung thêm 'khả năng thông thạo' để API của tôi?

+0

Cách tiếp cận "giao diện thông thạo" này ... trông không thân thiện với các ngôn ngữ được nhập tĩnh. Mặc dù tôi chắc chắn với một số mã gói và thừa kế bạn có thể làm điều đó với Scala, nó sẽ không được an toàn như có 'getOrders' và' getCustomers' một cách riêng biệt, thay vì 'get (" orders ")' và 'get (" customers ")' sử dụng cùng phương thức 'get'. –

+0

Cảm ơn Dan - nhưng tôi sẽ không lo lắng quá nhiều về cú pháp 'get (" slug ", id)' - đây thực sự không phải là câu hỏi của tôi. Trong mọi trường hợp có một chế độ an toàn hơn trong thư viện trông giống như 'MyTargetApi.orders.get (id)' –

+0

Cá nhân tôi nghĩ bạn nên cung cấp một ví dụ điển hình hơn về một số mã thông thạo và chính xác bit mà bạn nghĩ là không hoạt động. Tại thời điểm này, nó chỉ xuất hiện từ câu hỏi của bạn mà bạn không thực sự biết những gì thông thạo có nghĩa là –

Trả lời

7

Dường như bạn đang xử lý API phía khách hàng của một giao tiếp kiểu còn lại. Phương thức get của bạn có vẻ là phương pháp kích hoạt chu kỳ yêu cầu/phản hồi thực tế. Dường như bạn sẽ phải đối phó với điều này:

  • thuộc tính của các phương tiện giao thông (như các thông tin, mức độ debug, xử lý lỗi)
  • dữ liệu cung cấp cho các đầu vào (id của bạnloại của kỷ lục (đặt hàng hoặc khách hàng)
  • làm điều gì đó với kết quả

tôi nghĩ rằng đối với các thuộc tính của phương tiện giao thông, bạn có thể đặt một số của nó vào constructor của MyTargetApi obj vv, nhưng bạn cũng có thể tạo một truy vấn đối tượng đó sẽ lưu trữ những cho một truy vấn đơn và có thể được đặt trong một thạo cách sử dụng một phương pháp query():

MyTargetApi.query().debugPrint(verbose=true).throwIfError() 

này sẽ quay trở lại một số đối tượng stateful Query rằng lưu trữ giá trị cho mức nhật ký, xử lý lỗi.Đối với việc cung cấp dữ liệu cho đầu vào, bạn cũng có thể sử dụng đối tượng truy vấn để thiết lập những giá trị đó nhưng thay vì trả lại phản ứng của bạn trả về một QueryResult:

class Query { 
    def debugPrint(verbose: Boolean): this.type = { _verbose = verbose; this } 
    def throwIfError(): this.type = { ... } 
    def get(tpe: String, id: Int): QueryResult[RestfulResponse] = 
    new QueryResult[RestfulResponse] { 
     def run(): RestfulResponse = // code to make rest call goes here 
    } 
} 

trait QueryResult[A] { self => 
    def map[B](f: (A) => B): QueryResult[B] = new QueryResult[B] { 
    def run(): B = f(self.run()) 
    } 
    def flatMap[B](f: (A) => QueryResult[B]) = new QueryResult[B] { 
    def run(): B = f(self.run()).run() 
    } 
    def run(): A 
} 

Sau đó, để cuối cùng có được kết quả bạn gọi run. Vì vậy, vào cuối ngày, bạn có thể gọi nó như thế này:

MyTargetApi.query() 
    .debugPrint(verbose=true) 
    .throwIfError() 
    .get("customers", 22) 
    .map(resp => resp._3.map(_.length)) // body 
    .run() 

Mà phải là một yêu cầu tiết sẽ báo lỗi ra vào vấn đề, lấy khách hàng với id 22, giữ cho cơ thể và có được chiều dài của nó như một Option[Int].

Ý tưởng là bạn có thể sử dụng map để xác định tính toán trên kết quả mà bạn chưa có. Nếu chúng tôi thêm flatMap vào nó, thì bạn cũng có thể kết hợp hai tính toán từ hai truy vấn khác nhau.

+0

Wow - cảm ơn rất lớn ** huynhjl ** vì đã đi qua câu nói khó hiểu của tôi và đưa câu trả lời này lại với nhau. Nó cực kỳ hữu ích - nó cho thấy cách xác định một giao diện thông thạo có thể trả về kiểu 'RestfulResponse' của tôi hoặc thông qua' map' có thể áp dụng tính toán tiếp theo và trả về nó. Tôi có thể xác nhận rằng phương thức 'query()' gốc mà bạn đề cập là một phần của 'MyTargetApi' và chỉ trả về một đối tượng' new Query() '? Ngoài ra nó sẽ là quá nhiều để yêu cầu để xem định nghĩa 'flatMap'? Cảm ơn một lần nữa! –

+1

@AlexDean, tôi đã thêm 'flatMap'. Có 'query()' sẽ là một phần của 'MyTargetApi' ban đầu của bạn và trả về một đối tượng' Query' mới. Xin vui lòng, sử dụng câu trả lời của tôi chỉ cho một số ý tưởng. Tôi cũng mời bạn xem http://engineering.foursquare.com/2011/01/21/rogue-a-type-safe-scala-dsl-for-querying-mongodb/ và nói chung bất kỳ giao diện ORM và nosql nào hoặc wrapper được viết cho Scala để có thêm cảm hứng. – huynhjl

+0

Rất cám ơn ** huynhjl **. Tôi đã sử dụng [nguồn Squeryl] (https://github.com/max-l/Squeryl/tree/master/src/main/scala/org/squeryl) để lấy cảm hứng, nhưng tôi chắc chắn sẽ kiểm tra Rogue và một số công cụ ORM/NoSQL khác nữa ... –

3

Thành thật mà nói, tôi nghĩ có vẻ như bạn cần cảm nhận được nhiều hơn một chút vì ví dụ này không phải là rõ ràng là chức năng, cũng như không thông thạo. Có vẻ như bạn có thể đang trộn lẫn với lưu ý không phải là idempotent theo nghĩa là phương pháp debugPrint của bạn có lẽ là đang thực hiện I/O và throwIfError là trường hợp ngoại lệ ném. Đó có phải là ý bạn không?

Nếu bạn đang đề cập đến việc một người xây dựng trạng thái độc lập có hoạt động hay không, câu trả lời là "không theo nghĩa thuần túy nhất". Tuy nhiên, lưu ý rằng người xây dựng không phải là nhà nước.

case class Person(name: String, age: Int) 

Thứ nhất; điều này có thể được tạo ra sử dụng tên thông số:

Person(name="Oxbow", age=36) 

Hoặc, một người thợ xây không quốc tịch:

object Person { 
    def withName(name: String) 
    = new { def andAge(age: Int) = new Person(name, age) } 
} 

Hey mau:

scala> Person withName "Oxbow" andAge 36 

Đối với việc bạn sử dụng chuỗi untyped để xác định các truy vấn bạn đang làm; đây là hình thức nghèo nàn trong một ngôn ngữ đánh máy tĩnh.Hơn thế nữa, không có nhu cầu:

sealed trait Query 
case object orders extends Query 

def get(query: Query): Result 

Hey mau:

api get orders 

Mặc dù, tôi nghĩ rằng đây là một ý tưởng tồi - bạn không nên có một phương pháp duy nhất mà có thể cung cấp cho bạn trở lại notionally hoàn toàn loại khác nhau của kết quả


Để kết luận: cá nhân tôi nghĩ rằng không có lí do gì mà lưu loát và chức năng không thể trộn lẫn, vì chức năng chỉ cho thấy sự thiếu hụt của nhà nước có thể thay đổi một nd các sở thích mạnh mẽ cho chức năng idempotent để thực hiện logic của bạn trong

Dưới đây là một cho bạn:.

args.map(_.toInt) 

args map toInt 

tôi cho rằng thứ hai là thông thạo hơn. Có thể nếu bạn xác định:

val toInt = (_ : String).toInt 

Tức là; nếu bạn định nghĩa một hàm. Tôi tìm thấy các chức năng và sự lưu loát kết hợp rất tốt trong Scala.

+0

Hi ** oxbow_lakes ** - nhiều người cảm ơn vì đã dành thời gian để đưa câu trả lời này lại với nhau. Bạn đã đúng - câu nói của tôi rất nghèo nàn, xin lỗi vì mọi sự nhầm lẫn. Tôi đồng ý rằng chức năng và thông thạo là hoàn toàn tương thích - và cảm ơn bạn vì các ví dụ về các nhà xây dựng nhà nước và nhà nước. Trên các chuỗi không được phân loại cho 'get()' ing các tài nguyên HTTP - Tôi đồng ý, đó là một ý tưởng tồi, tôi sẽ loại bỏ khả năng đó khỏi API (hoặc ít nhất là đánh dấu nó là 'không an toàn', kiểu Haskell). –

0

Bạn có thể thử có get() trả về một đối tượng wrapper mà có thể giống như thế này

type RestfulResponse = (Int, List[String], Option[String]) 

class ResponseWrapper(private rr: RestfulResponse /* and maybe some flags as additional arguments, or something? */) { 

    def get : RestfulResponse = rr 

    def throwIfError : RestfulResponse = { 
     // Throw your exception if you detect an error 
     rr // And return the response if you didn't detect an error 
    } 

    def debugPrint(verbose: Boolean, /* whatever other parameters you had in mind */) { 
     // All of your debugging printing logic 
    } 

    // Any and all other methods that you want this API response to be able to execute 

} 

Về cơ bản, điều này cho phép bạn đặt câu trả lời của bạn thành một chứa có tất cả những phương pháp này tốt đẹp mà bạn muốn và nếu bạn chỉ muốn nhận được phản hồi được gói, bạn có thể gọi phương thức get() của trình bao bọc.

Tất nhiên, nhược điểm của việc này là bạn sẽ cần thay đổi API của mình một chút, nếu điều đó đáng lo ngại đối với bạn. Vâng ... bạn có lẽ có thể tránh cần phải thay đổi API của bạn, trên thực tế, nếu bạn, thay vào đó, tạo một chuyển đổi ngầm từ RestfulResponse thành ResponseWrapper và ngược lại. Đó là điều đáng xem xét.

+0

Và theo cách nào đây là "phong cách chức năng" mà OP đang yêu cầu? Bạn đang ném lỗi và thực hiện I/O –

+1

@oxbox_lakes Bạn chính xác; nó không hoạt động, nhưng hầu hết các chương trình thực tế phải thỏa hiệp về chức năng. Tôi nghĩ đây là giải pháp tốt nhất để làm những gì anh ta hỏi. Cá nhân tôi, tôi khuyên bạn nên thay đổi API, nhưng đó không phải là lời kêu gọi của tôi. Nếu anh ta muốn ném lỗi và thực hiện I/O, tôi là ai để nói anh ta không nên? – Destin

+0

Nhưng đó là câu hỏi cụ thể của ông: "sự pha trộn thông thạo và chức năng như thế nào?". Mà bạn không có cách nào trả lời. –

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