Làm thế nào bạn có thể phân hủy một constructor?


21

Hãy nói rằng tôi có một lớp Enemy và hàm tạo sẽ trông giống như:

public Enemy(String name, float width, float height, Vector2 position, 
             float speed, int maxHp, int attackDamage, int defense... etc.){}

Điều này có vẻ tệ bởi vì hàm tạo có rất nhiều tham số, nhưng khi tôi tạo một đối tượng Enemy, tôi cần chỉ định tất cả những điều này. Tôi cũng muốn các thuộc tính này trong lớp Enemy, để tôi có thể lặp qua danh sách của chúng và nhận / đặt các tham số này. Tôi đã nghĩ có thể phân nhóm Enemy vào EnemyB, EnemyA, trong khi mã hóa maxHp của chúng và các thuộc tính cụ thể khác, nhưng sau đó tôi sẽ mất quyền truy cập vào các thuộc tính được mã hóa cứng của chúng nếu tôi muốn lặp qua danh sách Enemy (bao gồm EnemyA, EnemyB's và Kẻ thù).

Tôi chỉ đang cố gắng học cách viết mã sạch. Nếu nó làm cho một sự khác biệt, tôi làm việc trong Java / C ++ / C #. Bất kỳ điểm nào đi đúng hướng đều được đánh giá cao.


5
Không có gì xấu khi có một hàm tạo liên kết tất cả các thuộc tính. Trong thực tế, trong một số môi trường kiên trì, nó là bắt buộc. Không có gì nói rằng bạn không thể có nhiều nhà xây dựng, có lẽ với phương thức kiểm tra tính hợp lệ sẽ được gọi sau khi thực hiện xây dựng mảnh.
BobDalgleish

1
Tôi phải đặt câu hỏi nếu bạn từng có ý định xây dựng các đối tượng Kẻ thù bằng mã bằng chữ. Nếu bạn không và tôi không hiểu lý do tại sao, thì hãy xây dựng các hàm tạo lấy dữ liệu từ giao diện cơ sở dữ liệu hoặc chuỗi tuần tự hóa hoặc ...
Zan Lynx


Câu trả lời:


58

Giải pháp là bó các tham số thành các loại hỗn hợp. Chiều rộng và Chiều cao có liên quan về mặt khái niệm - chúng chỉ định kích thước của kẻ thù và thường sẽ cần thiết với nhau. Chúng có thể được thay thế bằng một Dimensionsloại, hoặc có thể là một Rectangleloại cũng bao gồm vị trí. Mặt khác, nó có thể có ý nghĩa hơn để nhóm positionspeedthành một MovementDataloại, đặc biệt là nếu gia tốc sau này đi vào hình ảnh. Từ bối cảnh tôi giả sử maxHp, attackDamage, defense, vv cũng thuộc về nhau trong một Statskiểu. Vì vậy, một chữ ký sửa đổi có thể trông giống như thế này:

public Enemy(String name, Dimensions dimensions, MovementData movementData, Stats stats)

Các chi tiết đẹp về nơi vẽ các dòng sẽ phụ thuộc vào phần còn lại của mã của bạn và dữ liệu nào thường được sử dụng cùng nhau.


21
Tôi cũng sẽ nói thêm rằng việc có quá nhiều giá trị có thể cho thấy sự vi phạm Nguyên tắc Trách nhiệm Đơn lẻ. Và nhóm các giá trị vào các đối tượng cụ thể là bước đầu tiên để phân tách các trách nhiệm đó.
Euphoric

2
Tôi không nghĩ rằng danh sách các giá trị là vấn đề SRP; hầu hết trong số chúng có thể được dành cho các nhà xây dựng lớp cơ sở. Mỗi lớp trong hệ thống phân cấp có thể có một trách nhiệm duy nhất. Enemychỉ là lớp nhắm mục tiêu Player, nhưng lớp cơ sở chung của chúng Combatantcần các chỉ số chiến đấu.
MSalters

@MSalters Nó không nhất thiết chỉ ra vấn đề SRP, nhưng nó có thể. Nếu anh ta cần thực hiện đủ số lần bẻ khóa, các hàm đó có thể tìm đường vào lớp Enemy khi chúng phải là các hàm tĩnh / miễn phí (nếu anh ta sử dụng Dimensions/ MovementDatanhư các thùng chứa dữ liệu cũ đơn giản) hoặc các phương thức (nếu anh ta biến chúng thành dữ liệu trừu tượng các loại / đối tượng). Ví dụ, nếu anh ta chưa tạo ra một Vector2loại, anh ta có thể đã kết thúc việc học toán vectơ Enemy.
Doval

24

Bạn có thể muốn xem mẫu Builder . Từ liên kết (với một ví dụ về mẫu so với các lựa chọn thay thế):

[Mẫu] Trình tạo là một lựa chọn tốt khi thiết kế các lớp có các nhà xây dựng hoặc nhà máy tĩnh sẽ có nhiều hơn một số tham số, đặc biệt nếu hầu hết các tham số đó là tùy chọn. Mã máy khách dễ đọc và ghi hơn với các trình xây dựng so với mẫu trình xây dựng kính viễn vọng truyền thống và các trình xây dựng an toàn hơn nhiều so với JavaBeans.


4
Một đoạn mã ngắn sẽ hữu ích. Đây là một mô hình tuyệt vời để xây dựng các đối tượng hoặc cấu trúc phức tạp với nhiều đầu vào khác nhau. Bạn cũng có thể chuyên về các trình xây dựng, như EnemyABuilder, EnemyBBuilder, v.v ... đóng gói các thuộc tính được chia sẻ khác nhau. Đây là loại mặt trái của mẫu Factory (như được trả lời bên dưới), nhưng sở thích cá nhân của tôi là dành cho Builder.
Rob

1
Cảm ơn, cả mẫu Builder và mẫu Factory đều trông giống như chúng hoạt động tốt với những gì tôi đang cố gắng thực hiện. Tôi nghĩ rằng sự kết hợp giữa Builder / Factory và gợi ý của Doval có thể là thứ tôi đang tìm kiếm. Chỉnh sửa: Tôi đoán tôi chỉ có thể đánh dấu một câu trả lời; Tôi sẽ đưa nó cho Doval vì nó trả lời câu hỏi chủ đề, nhưng những câu hỏi khác cũng hữu ích không kém cho vấn đề cụ thể của tôi. Cảm ơn tất cả.
Travis

Tôi nghĩ điều đáng chú ý là nếu ngôn ngữ của bạn hỗ trợ các kiểu ảo, thì bạn có thể viết một mẫu trình xây dựng để thực thi rằng một số / tất cả các hàm SetX được gọi. Nó cũng cho phép một người đảm bảo rằng họ chỉ được gọi một lần nữa (nếu muốn).
Thomas Eding

1
@ Mark16 Như đã đề cập trong liên kết, > Mẫu Builder mô phỏng các tham số tùy chọn có tên như được tìm thấy trong Ada và Python. Bạn đã đề cập rằng bạn cũng sử dụng C # trong câu hỏi và ngôn ngữ đó hỗ trợ các đối số có tên / tùy chọn (kể từ C # 4.0), vì vậy đó có thể là một tùy chọn khác.
Bob

5

Sử dụng các lớp con để đặt trước một số giá trị là không mong muốn. Chỉ phân lớp khi một loại kẻ thù mới có hành vi khác hoặc thuộc tính mới.

Mẫu nhà máy thường được sử dụng để trừu tượng hóa lớp chính xác được sử dụng, nhưng nó cũng có thể được sử dụng để cung cấp một mẫu để tạo đối tượng:

class EnemyFactory {

    // each of these methods is essentially a template for a kind of enemy

    Enemy enemyA(String name, ...) {
        return new Enemy(name, ..., presetValue, ...);
    }

    Enemy enemyB(String name, ...) {
        return new Enemy(name, ..., otherValue, ...);
    }

    Enemy enemyC(String name, ...) {
        return new EnemySubclass(name, ..., otherValue, ...);
    }

    ...
}

EnemyFactory factory = new EnemyFactory();
Enemy a = factory.enemyA("fred", ...);
Enemy b = factory.enemyB("willy", ...);

0

Tôi sẽ dành lớp phụ cho các lớp đại diện cho đối tượng mà bạn có thể muốn sử dụng độc lập, ví dụ: lớp nhân vật trong đó tất cả các nhân vật, không chỉ kẻ thù có tên, tốc độ, maxHp hoặc một lớp để thể hiện các họa tiết có sự hiện diện trên màn hình, Chiều cao, vị trí.

Tôi không thấy bất cứ điều gì sai với một hàm tạo có nhiều tham số đầu vào nhưng nếu bạn muốn tách nó ra một chút thì bạn có thể có một hàm tạo thiết lập hầu hết các tham số và một hàm tạo khác (quá tải) có thể được sử dụng để đặt những cái cụ thể và đặt những cái khác thành giá trị mặc định.

Tùy thuộc vào ngôn ngữ bạn chọn sử dụng, một số có thể đặt giá trị mặc định cho các tham số đầu vào của hàm tạo của bạn như:

Enemy(float height = 42, float width = 42);

0

Một ví dụ mã để thêm vào câu trả lời của Rory Hunter (bằng Java):

public class Enemy{
   private String name;
   private float width;
   ...

   public static class Builder{
       private Enemy instance;

       public Builder(){
           this.instance = new Enemy();
       }


       public Builder withName(String name){
           instance.name = name;
           return this;
       }

       ...

       public Enemy build(){
           return instance;
       }
   }
}

Bây giờ, bạn có thể tạo các phiên bản mới của Enemy như thế này:

Enemy myEnemy = new Enemy.Builder().withName("John").withX(x).build();

1
Lập trình viên là các câu hỏi khái niệm tour và câu trả lời dự kiến ​​sẽ giải thích mọi thứ . Ném các đoạn mã thay vì giải thích giống như sao chép mã từ IDE sang bảng trắng: nó có thể trông quen thuộc và thậm chí đôi khi có thể hiểu được, nhưng nó cảm thấy kỳ lạ ... chỉ là lạ. Bảng trắng không có trình biên dịch
gnat
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.