Làm thế nào để cấu trúc lại một chương trình OO thành một chức năng?


26

Tôi đang gặp khó khăn trong việc tìm kiếm tài nguyên về cách viết chương trình theo phong cách chức năng. Chủ đề nâng cao nhất mà tôi có thể tìm thấy được thảo luận trực tuyến là sử dụng kiểu gõ cấu trúc để cắt giảm phân cấp lớp; hầu hết chỉ cần đối phó với cách sử dụng map / Fold / less / etc để thay thế các vòng lặp bắt buộc.

Điều tôi thực sự muốn tìm là một cuộc thảo luận chuyên sâu về việc triển khai OOP của một chương trình không tầm thường, những hạn chế của nó và cách cấu trúc lại nó theo kiểu chức năng. Không chỉ là một thuật toán hoặc cấu trúc dữ liệu, mà một cái gì đó với một số vai trò và khía cạnh khác nhau - có lẽ là một trò chơi video. Nhân tiện, tôi đã đọc Lập trình chức năng trong thế giới thực của tác giả Tomas Petricek, nhưng tôi không muốn nhiều hơn nữa.


6
Tôi không nghĩ rằng nó có thể. bạn phải thiết kế lại (và viết lại) mọi thứ một lần nữa.
Bryan Chen

18
-1, bài đăng này bị sai lệch bởi giả định sai rằng OOP và phong cách chức năng là trái ngược nhau. Đó chủ yếu là các khái niệm trực giao và IMHO là một huyền thoại mà chúng không phải là. "Chức năng" trái ngược với "Thủ tục" và cả hai kiểu có thể được sử dụng cùng với OOP.
Doc Brown

11
@DocBrown, OOP phụ thuộc quá nhiều vào trạng thái đột biến. Các đối tượng không trạng thái không phù hợp với thực tiễn thiết kế OOP hiện tại.
SK-logic

9
@ SK-logic: khóa không phải là đối tượng không trạng thái, mà là đối tượng bất biến. Và ngay cả khi các đối tượng có thể thay đổi, chúng thường có thể được sử dụng trong một phần chức năng của hệ thống miễn là chúng không bị thay đổi trong bối cảnh cụ thể. Hơn nữa, tôi đoán bạn biết các đối tượng và đóng cửa có thể thay thế cho nhau. Vì vậy, tất cả điều này cho thấy OOP và "chức năng" không trái ngược nhau.
Doc Brown

12
@DocBrown: Tôi nghĩ rằng các cấu trúc ngôn ngữ là trực giao, trong khi các tư duy có xu hướng xung đột. Những người OOP có xu hướng hỏi "các đối tượng là gì và họ hợp tác như thế nào?"; Những người có chức năng có xu hướng hỏi "dữ liệu của tôi là gì và tôi muốn chuyển đổi nó như thế nào?". Những câu hỏi không giống nhau, và chúng dẫn đến những câu trả lời khác nhau. Tôi cũng nghĩ bạn đọc sai câu hỏi. Đó không phải là "quy tắc OOP và quy tắc FP, làm cách nào để loại bỏ OOP?", Đó là "Tôi nhận được OOP và tôi không nhận được FP, có cách nào để chuyển đổi chương trình OOP thành chức năng, vì vậy tôi có thể nhận được một số hiểu biết? ".
Michael Shaw

Câu trả lời:


31

Định nghĩa lập trình hàm

Phần giới thiệu về The Joy of Clojure cho biết như sau:

Lập trình hàm là một trong những thuật ngữ điện toán có định nghĩa vô định hình. Nếu bạn hỏi 100 lập trình viên về định nghĩa của họ, bạn có thể sẽ nhận được 100 câu trả lời khác nhau ...

Lập trình hàm quan tâm và tạo điều kiện cho ứng dụng và thành phần của các chức năng ... Để một ngôn ngữ được coi là chức năng, khái niệm chức năng của nó phải là hạng nhất. Các hàm hạng nhất có thể được lưu trữ, chuyển và trả lại giống như bất kỳ phần dữ liệu nào khác. Ngoài khái niệm cốt lõi này, [các định nghĩa về FP có thể bao gồm] độ tinh khiết, tính bất biến, đệ quy, sự lười biếng và tính minh bạch tham chiếu.

Lập trình trong Scala 2nd Edition p. 10 có định nghĩa sau:

Lập trình chức năng được hướng dẫn bởi hai ý chính. Ý tưởng đầu tiên là các hàm là các giá trị hạng nhất ... Bạn có thể truyền các hàm dưới dạng đối số cho các hàm khác, trả về chúng dưới dạng kết quả từ các hàm hoặc lưu trữ chúng trong các biến ...

Ý tưởng chính thứ hai của lập trình chức năng là các hoạt động của chương trình nên ánh xạ các giá trị đầu vào thành các giá trị đầu ra thay vì thay đổi dữ liệu tại chỗ.

Nếu chúng tôi chấp nhận định nghĩa đầu tiên, thì điều duy nhất bạn cần làm để làm cho mã của mình "hoạt động" là biến các vòng lặp của bạn từ trong ra ngoài. Định nghĩa thứ hai bao gồm sự bất biến.

Chức năng hạng nhất

Hãy tưởng tượng bạn hiện đang nhận được Danh sách Hành khách từ đối tượng Xe buýt của mình và bạn lặp đi lặp lại việc giảm tài khoản ngân hàng của mỗi hành khách bằng số tiền vé xe buýt. Cách chức năng để thực hiện hành động tương tự này là có một phương thức trên Bus, có thể được gọi là forEachPasbah có chức năng của một đối số. Sau đó, Bus sẽ lặp lại hành khách của mình, tuy nhiên, điều đó được thực hiện tốt nhất và mã khách hàng của bạn tính giá vé cho chuyến đi sẽ được đưa vào một chức năng và được chuyển cho forEachPasbah. Voila! Bạn đang sử dụng lập trình chức năng.

Bắt buộc:

for (Passenger p : Bus.getPassengers()) {
    p.debit(fare);
}

Chức năng (sử dụng chức năng ẩn danh hoặc "lambda" trong Scala):

myBus = myBus.forEachPassenger(p:Passenger -> { p.debit(fare) })

Phiên bản Scala có đường hơn:

myBus = myBus.forEachPassenger(_.debit(fare))

Hàm không hạng nhất

Nếu ngôn ngữ của bạn không hỗ trợ các chức năng hạng nhất, điều này có thể trở nên rất xấu. Trong Java 7 trở về trước, Bạn phải cung cấp giao diện "Đối tượng chức năng" như thế này:

// Java 8 has java.util.function.Consumer, but in earlier
// versions you have to roll your own:
public interface Consumer<T> {
    public void accept(T t);
}

Sau đó, lớp Bus cung cấp một trình vòng lặp nội bộ:

public void forEachPassenger(Consumer<Passenger> c) {
    for (Passenger p : passengers) {
        c.accept(p);
    }
}

Cuối cùng, bạn chuyển một đối tượng hàm ẩn danh cho Bus:

// Java 8 has syntactic sugar to make this look more like
// the Scala solution, but earlier versions require manually
// instantiating a "Function Object," in this case, a
// Consumer:
Bus.forEachPassenger(new Consumer<Passenger>() {
    @Override
    public void accept(final Passenger p) {
        p.debit(fare);
    }
}

Java 8 cho phép các biến cục bộ được nắm bắt phạm vi của một hàm ẩn danh, nhưng trong các phiên bản trước đó, bất kỳ biến thể nào như vậy phải được khai báo cuối cùng. Để giải quyết vấn đề này, bạn có thể cần phải tạo một lớp bao bọc MutableReference. Đây là một lớp dành riêng cho số nguyên cho phép bạn thêm bộ đếm vòng lặp vào đoạn mã trên:

public static class MutableIntWrapper {
    private int i;
    private MutableIntWrapper(int in) { i = in; }
    public static MutableIntWrapper ofZero() {
        return new MutableIntWrapper(0);
    }
    public int value() { return i; }
    public void increment() { i++; }
}

final MutableIntWrapper count = MutableIntWrapper.ofZero();
Bus.forEachPassenger(new Consumer<Passenger>() {
    @Override
    public void accept(final Passenger p) {
        p.debit(fare);
        count.increment();
    }
}

System.out.println(count.value());

Ngay cả với sự xấu xí này, đôi khi cũng có ích để loại bỏ logic phức tạp và lặp đi lặp lại từ các vòng lặp trải khắp chương trình của bạn bằng cách cung cấp một trình vòng lặp nội bộ.

Sự xấu xí này đã được sửa trong Java 8, nhưng việc xử lý các ngoại lệ được kiểm tra bên trong hàm hạng nhất vẫn thực sự xấu và Java vẫn mang giả định về tính biến đổi trong tất cả các bộ sưu tập của nó. Điều này đưa chúng ta đến các mục tiêu khác thường được liên kết với FP:

Bất biến

Mục 13 của Josh Bloch là "Thích bất biến." Mặc dù rác thông thường nói ngược lại, OOP có thể được thực hiện với các đối tượng bất biến, và làm như vậy làm cho nó tốt hơn nhiều. Chẳng hạn, String trong Java là bất biến. StringBuffer, OTOH cần có thể thay đổi để xây dựng Chuỗi bất biến. Một số nhiệm vụ, như làm việc với bộ đệm vốn đã yêu cầu khả năng biến đổi.

Độ tinh khiết

Mỗi hàm ít nhất phải có khả năng ghi nhớ - nếu bạn cung cấp cho nó các tham số đầu vào giống nhau (và nó không có đầu vào nào ngoài các đối số thực tế của nó), nó sẽ tạo ra cùng một đầu ra mỗi lần mà không gây ra "tác dụng phụ" như thay đổi trạng thái toàn cầu, thực hiện I / O, hoặc ném ngoại lệ.

Người ta đã nói rằng trong Lập trình chức năng, "một số điều ác thường được yêu cầu để hoàn thành công việc." Độ tinh khiết 100% thường không phải là mục tiêu. Giảm thiểu tác dụng phụ là.

Phần kết luận

Thực sự, trong tất cả các ý tưởng ở trên, tính bất biến đã là chiến thắng lớn nhất về các ứng dụng thực tế để đơn giản hóa mã của tôi - cho dù là OOP hay FP. Truyền các chức năng cho các trình vòng lặp là chiến thắng lớn thứ hai. Các Java 8 Lambdas tài liệu có lời giải thích tốt nhất của lý do tại sao. Đệ quy là tuyệt vời để xử lý cây. Sự lười biếng cho phép bạn làm việc với các bộ sưu tập vô hạn.

Nếu bạn thích JVM, tôi khuyên bạn nên xem Scala và Clojure. Cả hai đều là những diễn giải sâu sắc về lập trình hàm. Scala là loại an toàn với cú pháp hơi giống C, mặc dù nó thực sự có nhiều cú pháp giống với Haskell như với C. Clojure không an toàn kiểu và nó là Lisp. Gần đây tôi đã đăng một so sánh về Java, Scala và Clojure liên quan đến một vấn đề tái cấu trúc cụ thể. Sự so sánh của Logan Campbell khi sử dụng Trò chơi cuộc sống bao gồm Haskell và gõ Clojure.

PS

Jimmy Hoffa chỉ ra rằng lớp Bus của tôi có thể thay đổi. Thay vì sửa bản gốc, tôi nghĩ rằng điều này sẽ chứng minh chính xác loại tái cấu trúc câu hỏi này. Điều này có thể được khắc phục bằng cách biến mỗi phương thức trên Bus thành một nhà máy để sản xuất một Bus mới, mỗi phương thức trên Hành khách một nhà máy để sản xuất một Hành khách mới. Do đó, tôi đã thêm một kiểu trả về cho tất cả mọi thứ, điều đó có nghĩa là tôi sẽ sao chép java.util.feft.Factor của Java 8 thay vì giao diện Consumer:

public interface Function<T,R> {
    public R apply(T t);
    // Note: I'm leaving out Java 8's compose() method here for simplicity
}

Sau đó trên xe buýt:

public Bus mapPassengers(Function<Passenger,Passenger> c) {
    // I have to use a mutable collection internally because Java
    // does not have immutable collections that return modified copies
    // of themselves the way the Clojure and Scala collections do.
    List<Passenger> newPassengers = new ArrayList(passengers.size());
    for (Passenger p : passengers) {
        newPassengers.add(c.apply(p));
    }
    return Bus.of(driver, Collections.unmodifiableList(passengers));
}

Cuối cùng, đối tượng hàm ẩn danh trả về trạng thái đã sửa đổi của một thứ (một xe buýt mới với hành khách mới). Điều này giả định rằng p.debit () hiện trả về một Hành khách bất biến mới với số tiền ít hơn so với ban đầu:

Bus b = b.mapPassengers(new Function<Passenger,Passenger>() {
    @Override
    public Passenger apply(final Passenger p) {
        return p.debit(fare);
    }
}

Hy vọng rằng bây giờ bạn có thể tự đưa ra quyết định về cách bạn muốn tạo ra ngôn ngữ bắt buộc của mình và quyết định xem có nên thiết kế lại dự án của bạn bằng ngôn ngữ chức năng hay không. Trong Scala hoặc Clojure, các bộ sưu tập và các API khác được thiết kế để giúp lập trình chức năng dễ dàng. Cả hai đều có Java interop rất tốt, vì vậy bạn có thể trộn và kết hợp các ngôn ngữ. Trong thực tế, đối với khả năng tương tác của Java, Scala biên dịch các hàm lớp đầu tiên của nó thành các lớp ẩn danh gần như tương thích với các giao diện chức năng của Java 8. Bạn có thể đọc về các chi tiết trong Scala trong giáo phái Depth. 1.3.2 .


Tôi đánh giá cao nỗ lực, tổ chức và giao tiếp rõ ràng trong câu trả lời này; nhưng tôi phải có một chút vấn đề với một số kỹ thuật. Một trong những chìa khóa như được đề cập gần trên cùng là thành phần của các hàm, điều này quay trở lại lý do tại sao phần lớn các chức năng đóng gói bên trong các đối tượng không mang lại mục đích: Nếu một chức năng nằm trong một đối tượng, thì nó phải ở đó để hành động trên đối tượng đó; và nếu nó tác động lên đối tượng đó thì nó phải thay đổi bên trong. Bây giờ tôi sẽ tha thứ rằng không phải ai cũng yêu cầu sự minh bạch hoặc bất biến tham chiếu, nhưng nếu nó thay đổi đối tượng tại chỗ thì nó không còn cần phải trả lại
Jimmy Hoffa

Và ngay khi một hàm không trả về giá trị, đột nhiên hàm này không thể được kết hợp với các hàm khác và bạn mất tất cả sự trừu tượng của thành phần hàm. Bạn có thể có chức năng thay đổi đối tượng tại chỗ và sau đó trả lại đối tượng, nhưng nếu nó làm điều này tại sao không làm cho hàm lấy đối tượng làm tham số và giải phóng nó khỏi giới hạn của đối tượng mẹ? Được giải phóng khỏi đối tượng cha, nó cũng có thể hoạt động trên các loại khác, đây là một phần quan trọng khác của FP mà bạn đang thiếu: Nhập trừu tượng. forEachPasenger của bạn chỉ hoạt động chống lại hành khách ...
Jimmy Hoffa

1
Lý do bạn trừu tượng hóa mọi thứ để ánh xạ và thu nhỏ, và các hàm này không bị ràng buộc trong việc chứa các đối tượng là để chúng có thể được sử dụng trên vô số các loại thông qua đa hình tham số. Đó là sự nhầm lẫn của các khái niệm trừu tượng khác nhau mà bạn không tìm thấy trong các ngôn ngữ OOP thực sự định nghĩa FP và khiến nó có giá trị. Không phải là sự lười biếng, tính minh bạch tham chiếu, tính bất biến hay thậm chí là hệ thống loại HM là cần thiết để tạo ra FP, những thứ đó là tác dụng phụ của việc tạo ra các ngôn ngữ được định hướng cho thành phần chức năng nơi các chức năng có thể trừu tượng hóa các loại nói chung
Jimmy Hoffa

@JimmyHoffa Bạn đã chỉ trích rất công bằng ví dụ của tôi. Tôi đã bị quyến rũ bởi khả năng biến đổi bởi giao diện Người tiêu dùng Java8. Ngoài ra, định nghĩa chouser / Fogus của FP không bao gồm tính bất biến và tôi đã thêm định nghĩa Oderky / Spoon / Venners sau. Tôi đã để lại ví dụ ban đầu, nhưng đã thêm một phiên bản mới, không thay đổi trong phần "PS" ở phía dưới. No thật la xâu xi. Nhưng tôi nghĩ nó thể hiện các chức năng tác động lên các đối tượng để tạo ra các đối tượng mới thay vì thay đổi phần bên trong của bản gốc. Nhận xét tuyệt vời!
GlenPeterson

1
Cuộc trò chuyện này tiếp tục trên Bảng trắng: chat.stackexchange.com/transcript/message/11702383#11702383
GlenPeterson

12

Tôi có kinh nghiệm cá nhân "hoàn thành" điều này. Cuối cùng, tôi đã không nghĩ ra thứ gì đó hoàn toàn có chức năng, nhưng tôi đã nghĩ ra thứ gì đó mà tôi hài lòng. Đây là cách tôi đã làm:

  • Chuyển đổi tất cả trạng thái bên ngoài thành một tham số của hàm. EG: nếu phương thức của đối tượng sửa đổi x, hãy làm cho nó để phương thức được truyềnx thay vì gọi this.x.
  • Loại bỏ hành vi khỏi các đối tượng.
    1. Làm cho dữ liệu của đối tượng có thể truy cập công khai
    2. Chuyển đổi tất cả các phương thức thành các hàm mà đối tượng gọi.
    3. Có mã máy khách gọi đối tượng gọi hàm mới bằng cách truyền dữ liệu đối tượng. EG: Chuyển đổi x.methodThatModifiesTheFooVar()thànhfooFn(x.foo)
    4. Loại bỏ phương thức ban đầu khỏi đối tượng
  • Thay thế như nhiều vòng lặp như bạn có thể với cao hơn chức năng tự thích map, reduce, filtervv

Tôi không thể thoát khỏi trạng thái đột biến. Nó chỉ là quá không thành ngữ trong ngôn ngữ của tôi (JavaScript). Nhưng, bằng cách làm cho tất cả trạng thái được truyền vào và / hoặc trả lại, mọi chức năng đều có thể kiểm tra. Điều này khác với OOP khi việc thiết lập trạng thái sẽ mất quá nhiều thời gian hoặc tách biệt các phụ thuộc thường yêu cầu sửa đổi mã sản xuất trước tiên.

Ngoài ra, tôi có thể sai về định nghĩa, nhưng tôi nghĩ các hàm của tôi được minh bạch về mặt tham chiếu: Các hàm của tôi sẽ có cùng hiệu ứng với cùng một đầu vào.

Chỉnh sửa

Như bạn có thể thấy ở đây , không thể tạo một đối tượng thực sự bất biến trong JavaScript. Nếu bạn siêng năng và kiểm soát ai gọi mã của bạn, bạn có thể làm điều đó bằng cách luôn tạo một đối tượng mới thay vì thay đổi đối tượng hiện tại. Nó không đáng để tôi nỗ lực.

Nhưng nếu bạn đang sử dụng Java, bạn có thể sử dụng các kỹ thuật này để làm cho các lớp của bạn không thay đổi.


+1 Tùy thuộc vào chính xác những gì bạn đang cố gắng thực hiện, điều này có thể là xa như bạn có thể thực sự đi mà không thực hiện thay đổi thiết kế sẽ đi waaaay ngoài việc "tái cấu trúc".
Evicatos

@Evicatos: Tôi không biết, nếu JavaScript hỗ trợ tốt hơn cho trạng thái bất biến, tôi nghĩ giải pháp của tôi sẽ có chức năng như bạn có trong một ngôn ngữ chức năng động như Clojure. Một ví dụ về một cái gì đó sẽ yêu cầu một cái gì đó ngoài việc tái cấu trúc?
Daniel Kaplan

Tôi nghĩ rằng thoát khỏi trạng thái đột biến sẽ đủ điều kiện. Tôi không nghĩ đó chỉ là một câu hỏi về sự hỗ trợ tốt hơn trong ngôn ngữ, tôi nghĩ rằng việc chuyển từ biến đổi thành bất biến về cơ bản sẽ luôn đòi hỏi những thay đổi kiến ​​trúc cơ bản tạo thành một bản viết lại. Ymmv tùy thuộc vào định nghĩa của bạn về tái cấu trúc mặc dù.
Evicatos

@Evicatos xem bản chỉnh sửa của tôi
Daniel Kaplan

1
@tieTYT vâng, thật đáng buồn khi JS quá đột biến, nhưng ít nhất Clojure có thể biên dịch thành JavaScript: github.com/clojure/clojurescript
GlenPeterson

3

Tôi không nghĩ rằng thực sự có thể cấu trúc lại chương trình hoàn toàn - bạn sẽ phải thiết kế lại và thực hiện lại theo mô hình chính xác.

Tôi đã thấy tái cấu trúc mã được định nghĩa là "kỹ thuật có kỷ luật để tái cấu trúc một bộ mã hiện có, thay đổi cấu trúc bên trong của nó mà không thay đổi hành vi bên ngoài của nó".

Bạn có thể làm cho một số thứ có chức năng hơn, nhưng cốt lõi là bạn vẫn có một chương trình hướng đối tượng. Bạn không thể thay đổi các bit và miếng nhỏ để điều chỉnh nó theo một mô hình khác.


Tôi muốn nói thêm rằng một dấu ấn đầu tiên tốt là cố gắng minh bạch tham chiếu. Một khi bạn có điều này, bạn nhận được ~ 50% lợi ích của lập trình chức năng.
Daniel Gratzer

3

Tôi nghĩ rằng loạt bài viết này là chính xác những gì bạn muốn:

Hoàn toàn chức năng Retrogames

http://prog21.dadgum.com/23.html Phần 1

http://prog21.dadgum.com/24.html Phần 2

http://prog21.dadgum.com/25.html Phần 3

http://prog21.dadgum.com/26.html Phần 4

http://prog21.dadgum.com/37.html Theo dõi

Tóm tắt là:

Tác giả đề xuất một vòng lặp chính với các tác dụng phụ (tác dụng phụ phải xảy ra ở đâu đó, phải không?) Và hầu hết các chức năng trả về các bản ghi nhỏ bất biến chi tiết về cách chúng thay đổi trạng thái của trò chơi.

Tất nhiên, khi viết một chương trình trong thế giới thực, bạn sẽ trộn và kết hợp một số kiểu lập trình, sử dụng từng kiểu mà nó giúp ích nhiều nhất. Tuy nhiên, đó là một kinh nghiệm học tập tốt để thử viết một chương trình theo cách có chức năng / bất biến nhất và cũng viết nó theo cách spaghetti nhất, chỉ sử dụng các biến toàn cục :-) (làm như một thử nghiệm, không phải trong sản xuất, vui lòng)


2

Bạn có thể sẽ phải chuyển tất cả mã của mình ra ngoài vì OOP và FP có hai cách tiếp cận ngược nhau để tổ chức mã.

OOP tổ chức mã xung quanh các loại (các lớp): các lớp khác nhau có thể thực hiện cùng một hoạt động (một phương thức có cùng chữ ký). Do đó, OOP phù hợp hơn khi tập hợp các hoạt động không thay đổi nhiều trong khi các loại mới có thể được thêm vào rất thường xuyên. Ví dụ, hãy xem xét một thư viện GUI trong đó mỗi widget có một tập cố định các phương pháp ( hide(), show(), paint(), move(), và vân vân) nhưng widget mới có thể được thêm vào như là thư viện được mở rộng. Trong OOP, thật dễ dàng để thêm một loại mới (đối với một giao diện nhất định): bạn chỉ cần thêm một lớp mới và thực hiện tất cả các phương thức của nó (thay đổi mã cục bộ). Mặt khác, việc thêm một hoạt động (phương thức) mới vào một giao diện có thể yêu cầu thay đổi tất cả các lớp thực hiện giao diện đó (mặc dù kế thừa có thể làm giảm lượng công việc).

FP tổ chức mã xung quanh các hoạt động (chức năng): mỗi chức năng thực hiện một số hoạt động có thể xử lý các loại khác nhau theo các cách khác nhau. Điều này thường đạt được bằng cách gửi về loại thông qua khớp mẫu hoặc một số cơ chế khác. Kết quả là, FP phù hợp hơn khi tập hợp các loại ổn định và các hoạt động mới được thêm vào thường xuyên hơn. Lấy ví dụ một bộ định dạng hình ảnh cố định (GIF, JPEG, v.v.) và một số thuật toán mà bạn muốn thực hiện. Mỗi thuật toán có thể được thực hiện bởi một chức năng hoạt động khác nhau tùy theo loại hình ảnh. Thêm một thuật toán mới là dễ dàng vì bạn chỉ cần thực hiện một chức năng mới (thay đổi mã cục bộ). Thêm một định dạng mới (loại) yêu cầu sửa đổi tất cả các chức năng bạn đã triển khai cho đến nay để hỗ trợ nó (thay đổi không cục bộ).

Điểm mấu chốt: OOP và FP khác nhau về cơ bản trong cách họ tổ chức mã và việc thay đổi thiết kế OOP thành thiết kế FP sẽ liên quan đến việc thay đổi tất cả mã của bạn để phản ánh điều này. Nó có thể là một bài tập thú vị, mặc dù. Xem thêm các ghi chú bài giảng cho cuốn sách SICP được trích dẫn bởi mikemay, đặc biệt là các slide 13.1.5 đến 13.1.10.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.