ตามที่ระบุไว้ในคำตอบและความคิดเห็นค่อนข้างน้อย DTOs มีความเหมาะสมและมีประโยชน์ในบางสถานการณ์โดยเฉพาะอย่างยิ่งในการถ่ายโอนข้อมูลข้ามขอบเขต (เช่นการทำให้เป็นอนุกรมกับ JSON เพื่อส่งผ่านบริการเว็บ) สำหรับส่วนที่เหลือของคำตอบนี้ฉันจะเพิกเฉยและพูดคุยเกี่ยวกับคลาสโดเมนและวิธีที่พวกเขาสามารถออกแบบมาเพื่อลดตัวรับและตัวตั้งค่า (ถ้าไม่กำจัด) และยังมีประโยชน์ในโครงการขนาดใหญ่ ฉันจะไม่พูดถึงว่าทำไมลบตัวรับหรือผู้ตั้งค่าหรือเมื่อต้องทำเช่นนั้นเพราะสิ่งเหล่านั้นเป็นคำถามของพวกเขาเอง
ยกตัวอย่างเช่นสมมติว่าโปรเจคของคุณเป็นเกมกระดานเช่น Chess หรือ Battleship คุณอาจมีหลายวิธีในการแสดงสิ่งนี้ในเลเยอร์การนำเสนอ (แอปคอนโซลบริการเว็บ GUI ฯลฯ ) แต่คุณมีโดเมนหลักด้วย ชั้นหนึ่งที่คุณอาจมีคือCoordinate
แทนตำแหน่งบนกระดาน วิธีการ "ชั่วร้าย" ในการเขียนจะเป็น:
public class Coordinate
{
public int X {get; set;}
public int Y {get; set;}
}
(ฉันจะเขียนตัวอย่างโค้ดใน C # มากกว่า Java เพื่อความกะทัดรัดและเพราะฉันคุ้นเคยกับมันมากขึ้นหวังว่านั่นไม่ใช่ปัญหาแนวคิดเหมือนกันและการแปลควรเป็นเรื่องง่าย)
การลบตัวตั้งค่า: ความไม่เข้ากัน
ในขณะที่ประชาชนทะเยอทะยานและ setters ทั้งสองมีปัญหาอาจ setters เป็น "ความชั่วร้าย" ของทั้งสอง พวกมันมักจะกำจัดได้ง่ายกว่า กระบวนการนี้ง่ายเพียงตั้งค่าจากภายในตัวสร้าง วิธีการใด ๆ ที่กลายพันธุ์ก่อนหน้าวัตถุควรส่งกลับผลลัพธ์ใหม่ ดังนั้น:
public class Coordinate
{
public int X {get; private set;}
public int Y {get; private set;}
public Coordinate(int x, int y)
{
X = x;
Y = y;
}
}
โปรดทราบว่าวิธีนี้ไม่ได้ป้องกันวิธีอื่น ๆ ในคลาสการกลายพันธุ์ X และ Y เพื่อให้ไม่เปลี่ยนรูปอย่างเข้มงวดมากขึ้นคุณสามารถใช้readonly
( final
ใน Java) แต่ไม่ว่าจะด้วยวิธีใดก็ตามไม่ว่าคุณจะทำให้คุณสมบัติของคุณไม่เปลี่ยนรูปแบบได้อย่างแท้จริงหรือเพียงแค่ป้องกันไม่ให้เกิดการกลายพันธุ์ในที่สาธารณะโดยตรงผ่านตัวตั้งค่า ในสถานการณ์ส่วนใหญ่ที่ได้ผลดี
การเอา Getters ส่วนที่ 1: การออกแบบสำหรับพฤติกรรม
ด้านบนเป็นสิ่งที่ดีและดีสำหรับผู้ตั้งถิ่นฐาน แต่ในแง่ของผู้ได้รับผลประโยชน์เราได้ยิงตัวเองก่อนที่จะเริ่ม กระบวนการของเราคือการคิดว่าพิกัดคืออะไร - ข้อมูลที่เป็นตัวแทน - และสร้างคลาสที่อยู่รอบ ๆ นั้น แต่เราควรเริ่มจากพฤติกรรมที่เราต้องการจากผู้ประสานงาน กระบวนการนี้ได้รับความช่วยเหลือจาก TDD ซึ่งเราแยกคลาสออกมาเป็นแบบนี้เมื่อเรามีความต้องการเท่านั้นดังนั้นเราจึงเริ่มต้นด้วยพฤติกรรมที่ต้องการและทำงานจากที่นั่น
ดังนั้นสมมติว่าสถานที่แรกที่คุณพบว่าตัวเองต้องการCoordinate
สำหรับการตรวจจับการชน: คุณต้องการตรวจสอบว่าสองชิ้นครอบครองพื้นที่เดียวกันบนกระดานหรือไม่ นี่คือวิธี "ความชั่วร้าย" (ตัวสร้างถูกละเว้นเนื่องจากความกะทัดรัด):
public class Piece
{
public Coordinate Position {get; private set;}
}
public class Coordinate
{
public int X {get; private set;}
public int Y {get; private set;}
}
//...And then, inside some class
public bool DoPiecesCollide(Piece one, Piece two)
{
return one.X == two.X && one.Y == two.Y;
}
และนี่คือวิธีที่ดี:
public class Piece
{
private Coordinate _position;
public bool CollidesWith(Piece other)
{
return _position.Equals(other._position);
}
}
public class Coordinate
{
private readonly int _x;
private readonly int _y;
public bool Equals(Coordinate other)
{
return _x == other._x && _y == other._y;
}
}
( IEquatable
การใช้งานย่อเพื่อความเรียบง่าย) โดยการออกแบบสำหรับพฤติกรรมมากกว่าการสร้างแบบจำลองข้อมูลเราจึงสามารถลบผู้ได้รับ
โปรดทราบว่าสิ่งนี้เกี่ยวข้องกับตัวอย่างของคุณด้วย คุณอาจใช้ ORM หรือแสดงข้อมูลลูกค้าในเว็บไซต์หรือบางสิ่งบางอย่างซึ่งCustomer
DTO บางประเภทอาจเหมาะสม แต่เพียงเพราะระบบของคุณมีลูกค้าและพวกเขาจะแสดงในรูปแบบข้อมูลไม่ได้หมายความว่าคุณควรมีCustomer
คลาสในโดเมนของคุณ บางทีในขณะที่คุณออกแบบพฤติกรรมเราอาจจะปรากฏ แต่ถ้าคุณต้องการหลีกเลี่ยงผู้ได้รับอย่าสร้างสิ่งที่ว่างเปล่าไว้ล่วงหน้า
การลบ Getters ตอนที่ 2: พฤติกรรมภายนอก
ดังนั้นข้างต้นเป็นการเริ่มต้นที่ดี แต่ไม่ช้าก็เร็วคุณอาจพบสถานการณ์ที่คุณมีพฤติกรรมที่เกี่ยวข้องกับคลาสซึ่งในบางวิธีขึ้นอยู่กับสถานะของคลาส แต่ไม่ได้อยู่ในคลาส การเรียงลำดับของพฤติกรรมนี้คือสิ่งที่โดยทั่วไปอาศัยอยู่ในชั้นบริการของแอปพลิเคชันของคุณ
Coordinate
ยกตัวอย่างของเราในที่สุดคุณจะต้องการเป็นตัวแทนเกมของคุณให้กับผู้ใช้และนั่นอาจหมายถึงการวาดภาพบนหน้าจอ ตัวอย่างเช่นคุณอาจมีโครงการ UI ที่ใช้Vector2
เพื่อแทนจุดบนหน้าจอ แต่มันจะไม่เหมาะสมสำหรับCoordinate
ชั้นเรียนที่จะดูแลการแปลงจากจุดประสานงานไปยังจุดบนหน้าจอซึ่งจะนำเรื่องที่เกี่ยวข้องกับการนำเสนอทุกประเภทมาไว้ในโดเมนหลักของคุณ น่าเสียดายที่สถานการณ์ประเภทนี้มีอยู่ในการออกแบบ OO
ตัวเลือกแรกซึ่งได้รับการคัดเลือกเป็นอย่างมากเป็นเพียงการเปิดโปงเจ้าพ่อและพูดกับมันด้วย นี่คือข้อดีของความเรียบง่าย แต่เนื่องจากเรากำลังพูดถึงการหลีกเลี่ยงผู้ทะเลาะกันเราจะปฏิเสธเรื่องนี้และดูว่ามีทางเลือกอื่นอีกหรือไม่
ตัวเลือกที่สองคือการเพิ่ม.ToDTO()
วิธีการบางอย่างในชั้นเรียนของคุณ สิ่งนี้หรือสิ่งที่คล้ายกันอาจมีความจำเป็นอยู่ดีตัวอย่างเช่นเมื่อคุณต้องการบันทึกเกมที่คุณต้องใช้ในการจับภาพสถานะทั้งหมดของคุณ แต่ความแตกต่างระหว่างการทำสิ่งนี้กับบริการของคุณและเพียงแค่การเข้าถึงผู้ทะเยอทะยานโดยตรงนั้นมีความสวยงามไม่มากก็น้อย มันยังมี "ความชั่วร้าย" อยู่มาก
ตัวเลือกที่สาม - ซึ่งฉันได้เห็นการสนับสนุนจากZoran Horvatในวิดีโอ Pluralsight สองสามรายการคือการใช้รูปแบบผู้เข้าชมที่ดัดแปลงแล้ว นี่เป็นการใช้รูปแบบและรูปแบบที่ผิดปกติและฉันคิดว่าระยะทางของผู้คนจะแตกต่างกันอย่างมากไม่ว่าจะเป็นการเพิ่มความซับซ้อนให้กับผลประโยชน์ที่ไม่ได้รับจริง แนวคิดนี้ใช้รูปแบบผู้เข้าชมมาตรฐานเป็นหลัก แต่ให้Visit
วิธีการระบุสถานะที่พวกเขาต้องการเป็นพารามิเตอร์แทนที่จะเป็นคลาสที่พวกเขากำลังเยี่ยมชม ตัวอย่างสามารถพบได้ที่นี่
สำหรับปัญหาของเราการแก้ปัญหาโดยใช้รูปแบบนี้จะเป็น:
public class Coordinate
{
private readonly int _x;
private readonly int _y;
public T Transform<T>(IPositionTransformer<T> transformer)
{
return transformer.Transform(_x,_y);
}
}
public interface IPositionTransformer<T>
{
T Transform(int x, int y);
}
//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
private readonly float _tileWidth;
private readonly float _tileHeight;
private readonly Vector2 _topLeft;
Vector2 Transform(int x, int y)
{
return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
}
}
ในขณะที่คุณอาจจะสามารถบอก_x
และ_y
ไม่ได้จริงๆห่อหุ้มใด ๆ เพิ่มเติม เราสามารถแยกมันออกได้โดยสร้างอันIPositionTransformer<Tuple<int,int>>
ที่คืนค่าพวกมันโดยตรง ขึ้นอยู่กับรสนิยมคุณอาจรู้สึกว่าสิ่งนี้ทำให้การออกกำลังกายทั้งหมดไม่มีจุดหมาย
แต่ด้วย getters ประชาชนมันเป็นเรื่องง่ายมากที่จะทำในสิ่งที่ไม่ถูกวิธีเพียงการดึงข้อมูลออกมาได้โดยตรงและใช้มันในการละเมิดบอกไม่ถาม ในขณะที่ใช้รูปแบบนี้เป็นเรื่องง่ายกว่าที่จะทำอย่างถูกวิธี: เมื่อคุณต้องการสร้างพฤติกรรมคุณจะเริ่มต้นโดยอัตโนมัติโดยการสร้างประเภทที่เกี่ยวข้อง การละเมิด TDA จะส่งกลิ่นอย่างเห็นได้ชัดและอาจต้องใช้วิธีแก้ไขที่ง่ายกว่าและดีกว่า ในทางปฏิบัติประเด็นเหล่านี้ทำให้ง่ายขึ้นในการทำสิ่งที่ถูกต้อง OO มากกว่าวิธี "ชั่ว" ที่ผู้กระตุ้นให้กำลังใจ
ในที่สุดแม้ว่ามันจะไม่ชัดเจนในตอนแรกอาจมีในความเป็นจริงจะเป็นวิธีการที่จะเปิดเผยความเพียงพอของสิ่งที่คุณต้องการเป็นพฤติกรรมเพื่อหลีกเลี่ยงการต้องเปิดเผยรัฐ ตัวอย่างเช่นการใช้รุ่นก่อนหน้าของเราCoordinate
ที่มีสมาชิกสาธารณะเพียงคนเดียวEquals()
(ในทางปฏิบัติมันจะต้องมีIEquatable
การใช้งานเต็มรูปแบบ) คุณสามารถเขียนคลาสต่อไปนี้ในเลเยอร์การนำเสนอของคุณ:
public class CoordinateToVectorTransformer
{
private Dictionary<Coordinate,Vector2> _coordinatePositions;
public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
{
for(int x=0; x<boardWidth; x++)
{
for(int y=0; y<boardWidth; y++)
{
_coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
}
}
}
private static Vector2 GetPosition(int x, int y)
{
//Some implementation goes here...
}
public Vector2 Transform(Coordinate coordinate)
{
return _coordinatePositions[coordinate];
}
}
มันจะเปิดออกอาจจะแปลกใจว่าพฤติกรรมทั้งหมดที่เราจริงๆจำเป็นจากการประสานงานเพื่อให้บรรลุเป้าหมายของเราได้รับการตรวจสอบความเท่าเทียมกัน! แน่นอนวิธีนี้เหมาะกับปัญหานี้และทำให้สมมติฐานเกี่ยวกับการใช้งาน / ประสิทธิภาพหน่วยความจำที่ยอมรับได้ เป็นเพียงตัวอย่างที่เหมาะกับโดเมนปัญหานี้โดยเฉพาะแทนที่จะเป็นพิมพ์เขียวสำหรับการแก้ปัญหาทั่วไป
และอีกครั้งความคิดเห็นจะแตกต่างกันไปในทางปฏิบัติว่านี่เป็นความซับซ้อนที่ไม่จำเป็น ในบางกรณีอาจไม่มีวิธีแก้ปัญหาเช่นนี้หรืออาจมีข้อผิดพลาดแปลก ๆ หรือซับซ้อนซึ่งในกรณีนี้คุณสามารถเปลี่ยนกลับไปเป็นสามข้อข้างต้นได้