Nó có vi phạm bất kỳ nguyên tắc OOP nào không nếu một hàm thành viên không sử dụng bất kỳ thuộc tính lớp / biến thành viên nào?


8

Tôi có một lớp hiện có tương tác có thể mở, đọc hoặc ghi vào một tệp. Tôi cần truy xuất một sửa đổi tập tin cho mục đích đó Tôi phải thêm một phương thức mới

Giả sử sau đây là định nghĩa lớp của tôi, nơi tôi muốn thêm một phương thức mới.

class IO_file
{
 std::string m_file_name;
 public:
 IO();
 IO(std::string file_name);

+ time_t get_mtime(file_name);
+       OR
+ time_t get_mtime();
};

Tôi có hai lựa chọn -

  1. Tạo một đối tượng trống và sau đó chuyển file_name trong đối số phương thức mà tôi muốn lấy thời gian sửa đổi tệp.

  2. Truyền tên tệp khi xây dựng đối tượng và chỉ cần gọi hàm thành viên sẽ hoạt động trên biến thành viên.

Cả hai tùy chọn sẽ phục vụ mục đích. Tôi cũng tin rằng cách tiếp cận thứ hai tốt hơn cách tiếp cận thứ nhất. Nhưng điều tôi không hiểu là thế nào ? Là cách tiếp cận đầu tiên thiết kế xấu vì nó không sử dụng biến thành viên? Nguyên tắc nào của thiết kế hướng đối tượng mà nó vi phạm? Nếu một hàm thành viên không sử dụng biến thành viên, thì hàm thành viên đó có nên luôn luôn được tạo thành tĩnh không?


OOP là về việc tách các mối quan tâm, ví dụ như tách khách hàng khỏi việc thực hiện thông qua một giao diện - nhưng cũng tách biệt sự lựa chọn thực hiện với việc sử dụng một giao diện, đồng thời cho phép nhiều triển khai có khả năng cùng tồn tại động. Sử dụng ví dụ chiến lược của @ CandiedOrange, người chọn chiến lược và người tiêu dùng chiến lược có thể được tách ra; trong khi đó với các phương thức tĩnh, mặc dù các mối quan tâm của máy khách và triển khai được tách biệt, vẫn có sự kết hợp chặt chẽ của máy khách với sự lựa chọn thực hiện (đơn).
Erik Eidt

2
Đó là thiết kế tồi mà tôi có thể làm IO("somefile.txt").get_mtime("someotherfile.txt"). Điều đó có nghĩa là gì?
dùng253751

Câu trả lời:


8

Nó có vi phạm bất kỳ hiệu trưởng OOP nào không nếu hàm thành viên không sử dụng bất kỳ thuộc tính lớp / biến thành viên nào?

Không.

OOP không quan tâm nếu hàm thành viên của bạn sử dụng hoặc không sử dụng các thuộc tính lớp hoặc biến thành viên. OOP quan tâm đến đa hình và không thực hiện mã hóa cứng. Các hàm tĩnh có công dụng của chúng nhưng một hàm không nên tĩnh đơn giản vì nó không phụ thuộc vào trạng thái đối tượng. Nếu đó là suy nghĩ của bạn tốt, nhưng đừng đổ lỗi cho OOP vì ý tưởng đó không đến từ OOP.

Là [nó] thiết kế xấu [không] sử dụng các biến thành viên?

Nếu bạn không cần phải nhớ trạng thái từ cuộc gọi đến cuộc gọi thì không có lý do chính đáng để sử dụng trạng thái.

Nguyên tắc nào của thiết kế hướng đối tượng [không] nó vi phạm?

Không ai.

Nếu một hàm thành viên không sử dụng biến thành viên thì hàm thành viên đó sẽ luôn luôn được tạo thành tĩnh?

Không. Suy nghĩ này có mũi tên hàm ý đi sai hướng.

  • Một hàm tĩnh không thể truy cập trạng thái thể hiện

  • Nếu hàm không có nhu cầu truy cập trạng thái thể hiện thì hàm có thể là tĩnh hoặc không tĩnh

Làm cho chức năng tĩnh ở đây là hoàn toàn tùy thuộc vào bạn. Nhưng nó sẽ làm cho nó giống như một toàn cầu hơn nếu bạn làm. Trước khi đi tĩnh hãy xem xét việc lưu trữ hàm trong một lớp không trạng thái. Nó linh hoạt hơn.


Tôi có ở đây một ví dụ OOP của hàm thành viên không sử dụng các thuộc tính lớp hoặc biến thành viên.

Hàm thành viên (và đó là lớp không trạng thái) :

#include <iostream>

class Strategy
{
public:
     virtual int execute (int a, int b) = 0; // execute() is a so-called pure virtual 
                                             // function. As a consequence, Strategy 
                                             // is a so-called abstract class.
};

 
Ba cách thực hiện khác nhau:

class ConcreteStrategyAdd:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyAdd's execute()\n";
        return a + b;
    }
};

class ConcreteStrategySubstract:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategySubstract's execute()\n";
        return a - b;
    }
};

class ConcreteStrategyMultiply:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyMultiply's execute()\n";
        return a * b;
    }
};

 
Một nơi để lưu trữ các lựa chọn thực hiện:

class Context
{
private:
    Strategy* pStrategy;

public:

    Context (Strategy& strategy)
        : pStrategy(&strategy)
    {
    }

    void SetStrategy(Strategy& strategy)
    {
        pStrategy = &strategy;
    }

    int executeStrategy(int a, int b)
    {
        return pStrategy->execute(a,b);
    }
};

 
Một ví dụ về việc sử dụng

int main()
{
    ConcreteStrategyAdd       concreteStrategyAdd;
    ConcreteStrategySubstract concreteStrategySubstract;
    ConcreteStrategyMultiply  concreteStrategyMultiply;

    Context context(concreteStrategyAdd);
    int resultA = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategySubstract);
    int resultB = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategyMultiply);
    int resultC = context.executeStrategy(3,4);

    std::cout << "\nresultA: " << resultA 
              << "\nresultB: " << resultB 
              << "\nresultC: " << resultC 
              << "\n";
}

Đầu ra:

Called ConcreteStrategyAdd's execute()
Called ConcreteStrategySubstract's execute()
Called ConcreteStrategyMultiply's execute()

resultA: 7       
resultB: -1       
resultC: 12

Và tất cả mà không execute()quan tâm đến trạng thái của bất kỳ đối tượng. Các Strategylớp thực sự là quốc tịch. Chỉ có nhà nước là trong Context. Các đối tượng không trạng thái là hoàn toàn tốt trong OOP.

Tìm thấy mã này ở đây .


1
+1 Liên quan đến thống kê "Suy nghĩ này có mũi tên hàm ý đi sai hướng." là hoàn hảo. Tôi đã từng làm việc tại một công ty trong đó nếu một chức năng không cần trạng thái thì nó sẽ luôn ở trạng thái tĩnh, nghĩa là khoảng 60% hoặc hơn (nếu không nhiều hơn) của ứng dụng cuối cùng là tĩnh. Nói về một cơn ác mộng.
Carson

Có thật không? Chúng tôi làm ngược lại chính xác tại công ty hiện tại của tôi (và tôi ủng hộ nó). Bất kỳ phương pháp có thể được thực hiện tĩnh là.
vườn

@gardenhead Nếu bạn muốn đưa ra một đối số truy cập xin vui lòng làm cho nó. Nói với tôi rằng cả một công ty đang làm điều đó không có nghĩa đó là một điều tốt.
candied_orange 17/03/2017

1
@candiedorange Tôi đồng ý rằng đó không phải là bằng chứng xác thực, nhưng tôi không muốn tham gia vào một cuộc chiến rực lửa. Nhưng vì bạn khăng khăng, đây là đối số phản biện của tôi: OOP rất tệ và càng ít sử dụng thì càng tốt. Vui mừng?
vườn

@gardenhead OOP là khủng nên tĩnh là tốt? Tôi có thể viết mã chức năng thuần túy mà không bao giờ sử dụng statics. Bạn có chắc là bạn không tìm kiếm một cuộc chiến nảy lửa?
candied_orange 17/03/2017

5

get_mtimesẽ có ý nghĩa hơn ở đây như là một chức năng độc lập hoặc là một staticchức năng, thay vì như bạn đã hiển thị ở đây.

Trên mtimehầu hết các hệ thống, một tệp được đọc, từ một cuộc gọi đến lstathoặc tương tự, và không yêu cầu một bộ mô tả tệp mở, do đó, không có nghĩa là nó là một hàm thành viên của một lớp được khởi tạo.


Vì mtime không yêu cầu mở bộ mô tả tệp, đây là lý do tôi muốn làm cho nó độc lập với biến thành viên m_file_name. Vì vậy, nó làm cho nhiều hơn để làm cho nó tĩnh trong lớp tôi hiểu phần đó. Nhưng tôi muốn hiểu từ quan điểm học thuật / phương tiện mà khái niệm OPP tôi vi phạm với lựa chọn thứ nhất.
irsis

2
@Rahul Thực sự không có câu trả lời hay cho điều đó. Ý tôi là, tôi cho rằng tôi có thể nói rằng nó vi phạm sự trừu tượng bằng cách ngụ ý sự phụ thuộc vào dữ liệu cá thể, nhưng đó không thực sự là sự trừu tượng. Thành thật mà nói, tôi nghĩ rằng bạn đang lo lắng quá nhiều về các vấn đề. Chỉ cần coi đó là một quy tắc chung rằng nếu một hàm thành viên không cần truy cập một thể hiện, thì nó không nên là một staticthành viên.
greyfade

1
+1 nhưng thực tế tôi biết đó không phải là một ý tưởng hay nhưng tôi muốn làm rõ sự hiểu biết của mình "tại sao"? Câu trả lời của CandidOrange giải thích nó.
irsis

2

Lý do mà tùy chọn thứ hai có vẻ tốt hơn theo bản năng (và, IMO, IS tốt hơn) là vì tùy chọn đầu tiên của bạn cung cấp cho bạn một đối tượng không thực sự đại diện cho bất cứ điều gì.

Bằng cách không chỉ định tên tệp, lớp IO_file của bạn thực sự chỉ là một đối tượng trừu tượng giống như một tệp cụ thể. Nếu bạn chuyển tên tệp khi bạn gọi phương thức, bạn cũng có thể chỉ cần cấu trúc lại toàn bộ phương thức thành một hàm thuần trôi nổi tự do; không có lợi ích thực sự nào để giữ nó gắn liền với một đối tượng mà bạn sẽ cần khởi tạo chỉ để sử dụng nó. Nó chỉ là một chức năng; nồi hơi khởi tạo đối tượng chỉ là một bước thêm bất tiện.

Mặt khác, một khi tên tệp được cung cấp cho bất kỳ phương thức nào bạn gọi trên đối tượng đó sẽ giống như các truy vấn về một trường hợp cụ thể của một sự vật. Đó là OO tốt hơn bởi vì các đối tượng của bạn có ý nghĩa thực sự và do đó, tiện ích.


2

Hãy dịch nó sang C. Đầu tiên chúng tôi lớp chúng tôi - bây giờ nó là một cấu trúc:

struct IO_file {
    char* m_file_name;
};

Chúng ta hãy đơn giản hóa các đoạn mã, xác định hàm xây dựng (bỏ qua rò rỉ bộ nhớ):

struct IO_file* IO_file(char* file_name) {
    struct IO_file* obj = malloc(sizeof(struct IO_file));
    obj.m_file_name = file_name;
    return obj;
}

Tùy chọn # 2 trông như thế này:

time_t get_mtime(struct IO_file*);

và được sử dụng như thế này:

time_t mtime = get_mtime(IO_file("some-file"));

Làm thế nào về lựa chọn số 1? Chà, có vẻ như thế này:

time_t get_mtime(struct IO_file* this, char* file_name);

Và nó được sử dụng như thế nào? Về cơ bản, bạn đang yêu cầu chuyển rác làm tham số đầu tiên:

time_t mtime = get_mtime((struct IO_file*)1245151325, "some-file");

Nó không có ý nghĩa nhiều, phải không?

Hệ thống đối tượng của C ++ che giấu nó, nhưng đối tượng cũng là một đối số của phương thức - một đối số con trỏ ẩn có tên this. Đây là tùy chọn số 1 không tốt - nó có một đối số không được sử dụng theo định nghĩa (các đối số xảy ra không được sử dụng, nhưng có thể được sử dụng trong phần ghi đè là OK). Các đối số như vậy không đóng góp gì trong khi làm phức tạp chữ ký, định nghĩa và cách sử dụng của hàm và gây nhầm lẫn cho người đọc mã đang suy nghĩ về những gì đối số phải làm, tại sao bạn có thể chuyển rác cho nó hay không và liệu bạn có thực tế không truyền rác / NULL/ đối tượng trống cho nó là nguyên nhân gây ra lỗi mà họ hiện đang cố gắng giải quyết. Spoiler - không phải vậy, nhưng họ vẫn sẽ lãng phí thời gian để khám phá khả năng đó.

Thực tế là đối số rác là ẩn có thể làm giảm hiệu ứng, nhưng nó vẫn ở đó và dễ dàng tránh được bằng cách tạo phương thức static.

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.