อ๊ะมีความเข้าใจผิดแปลก ๆ เกี่ยวกับสิ่งที่ OCP และ LSP และบางอย่างเกิดจากความไม่ตรงกันของคำศัพท์บางคำและตัวอย่างที่สับสน หลักการทั้งสองนั้นเป็นเพียง "สิ่งเดียวกัน" ถ้าคุณใช้มันด้วยวิธีเดียวกัน รูปแบบมักจะปฏิบัติตามหลักการในทางเดียวหรืออื่นมีข้อยกเว้นเล็กน้อย
ความแตกต่างจะถูกอธิบายเพิ่มเติมลงไป แต่ก่อนอื่นให้เราทำการดำน้ำในหลักการด้วยตนเอง
หลักการแบบเปิด (OCP)
ตามที่ลุงบ๊อบ :
คุณควรจะสามารถขยายพฤติกรรมการเรียนโดยไม่ต้องดัดแปลง
โปรดทราบว่าคำขยายในกรณีนี้ไม่ได้หมายความว่าคุณควรคลาสย่อยคลาสจริงที่ต้องการพฤติกรรมใหม่ ดูว่าฉันพูดถึงคำศัพท์แรกที่ไม่ตรงกันได้อย่างไร คำหลักextend
หมายถึงคลาสย่อยใน Java เท่านั้น แต่หลักการนั้นเก่ากว่า Java
ต้นฉบับมาจาก Bertrand Meyer ในปี 1988:
หน่วยงานซอฟต์แวร์ (คลาส, โมดูล, ฟังก์ชั่น, ฯลฯ ) ควรจะเปิดเพื่อขยาย แต่ปิดเพื่อการปรับเปลี่ยน
นี่มันเป็นที่ชัดเจนมากขึ้นว่าหลักการที่จะนำไปใช้กับหน่วยงานซอฟแวร์ ตัวอย่างที่ไม่ดีจะถูกแทนที่ด้วยเอนทิตีของซอฟต์แวร์ในขณะที่คุณกำลังแก้ไขโค้ดทั้งหมดแทนที่จะให้จุดส่วนขยายบางจุด พฤติกรรมของกิจการซอฟต์แวร์ตัวเองควรจะขยายและเป็นตัวอย่างที่ดีของที่นี่คือการดำเนินการตามกลยุทธ์รูปแบบ (เพราะมันเป็นที่ง่ายที่สุดที่จะแสดงของ GoF-รูปแบบพวง IMHO):
// Context is closed for modifications. Meaning you are
// not supposed to change the code here.
public class Context {
// Context is however open for extension through
// this private field
private IBehavior behavior;
// The context calls the behavior in this public
// method. If you want to change this you need
// to implement it in the IBehavior object
public void doStuff() {
if (this.behavior != null)
this.behavior.doStuff();
}
// You can dynamically set a new behavior at will
public void setBehavior(IBehavior behavior) {
this.behavior = behavior;
}
}
// The extension point looks like this and can be
// subclassed/implemented
public interface IBehavior {
public void doStuff();
}
ในตัวอย่างข้างต้นContext
จะถูกล็อคสำหรับการแก้ไขเพิ่มเติม โปรแกรมเมอร์ส่วนใหญ่อาจต้องการ subclass คลาสเพื่อขยาย แต่ที่นี่เราทำไม่ได้เพราะมันถือว่าพฤติกรรมของมันสามารถเปลี่ยนแปลงได้ผ่านสิ่งที่ใช้IBehavior
อินเตอร์เฟซ
คือระดับบริบทจะปิดสำหรับการปรับเปลี่ยน แต่เปิดให้บริการสำหรับส่วนขยาย อันที่จริงมันเป็นไปตามหลักการพื้นฐานอื่นเพราะเราวางพฤติกรรมที่มีองค์ประกอบของวัตถุแทนการสืบทอด:
"ชอบ" การจัดองค์ประกอบวัตถุ "เหนือ" การสืบทอดคลาส " (แก๊งสี่ 1995: 20)
ฉันจะให้ผู้อ่านอ่านหลักการนี้เพราะมันอยู่นอกขอบเขตของคำถามนี้ หากต้องการทำตัวอย่างต่อไปสมมติว่าเรามีการใช้อินเทอร์เฟซ IBehavior ต่อไปนี้:
public class HelloWorldBehavior implements IBehavior {
public void doStuff() {
System.println("Hello world!");
}
}
public class GoodByeBehavior implements IBehavior {
public void doStuff() {
System.out.println("Good bye cruel world!");
}
}
การใช้รูปแบบนี้เราสามารถปรับเปลี่ยนพฤติกรรมของบริบทที่รันไทม์ผ่านsetBehavior
วิธีการเป็นจุดส่วนขยาย
// in your main method
Context c = new Context();
c.setBehavior(new HelloWorldBehavior());
c.doStuff();
// prints out "Hello world!"
c.setBehavior(new GoodByeBehavior());
c.doStuff();
// prints out "Good bye cruel world!"
ดังนั้นเมื่อใดก็ตามที่คุณต้องการขยายคลาสบริบท "ปิด" ให้ทำโดยการทำคลาสย่อยโดยการพึ่งพาแบบ "เปิด" ซึ่งเป็นการทำงานร่วมกัน เห็นได้ชัดว่าไม่ใช่สิ่งเดียวกันกับการทำคลาสย่อยบริบทเอง แต่มันเป็น OCP LSP ไม่ได้กล่าวถึงเรื่องนี้เช่นกัน
การขยายด้วย Mixins แทนที่จะเป็นการสืบทอด
มีวิธีอื่นในการทำ OCP นอกเหนือจากคลาสย่อย วิธีหนึ่งคือให้คลาสของคุณเปิดสำหรับการขยายผ่านการใช้มิกซ์อิน สิ่งนี้มีประโยชน์เช่นในภาษาที่ใช้ต้นแบบมากกว่าใช้คลาส ความคิดคือการแก้ไขวัตถุแบบไดนามิกด้วยวิธีการหรือคุณลักษณะเพิ่มเติมตามความจำเป็นในคำอื่น ๆ วัตถุที่ผสมผสานหรือ "ผสมใน" กับวัตถุอื่น ๆ
นี่คือตัวอย่าง javascript ของมิกซ์อินที่แสดงเทมเพลต HTML แบบง่ายสำหรับจุดยึด:
// The mixin, provides a template for anchor HTML elements, i.e. <a>
var LinkMixin = {
render: function() {
return '<a href="' + this.link +'">'
+ this.content
+ '</a>;
}
}
// Constructor for a youtube link
var YoutubeLink = function(content, youtubeId) {
this.content = content;
this.setLink(this.youtubeId);
};
// Methods are added to the prototype
YoutubeLink.prototype = {
setLink: function(youtubeid) {
this.link = 'http://www.youtube.com/watch?v=' + youtubeid;
}
};
// Extend YoutubeLink prototype with the LinkMixin using
// underscore/lodash extend
_.extend(YoutubeLink.protoype, LinkMixin);
// When used:
var ytLink = new YoutubeLink("Cool Movie!", "idOaZpX8lnA");
console.log(ytLink.render());
// will output:
// <a href="http://www.youtube.com/watch?=vidOaZpX8lnA">Cool Movie!</a>
แนวคิดคือการขยายวัตถุแบบไดนามิกและข้อดีของสิ่งนี้คือวัตถุอาจใช้วิธีการร่วมกันแม้ว่าพวกเขาจะอยู่ในโดเมนที่แตกต่างกันโดยสิ้นเชิง ในกรณีดังกล่าวข้างต้นคุณสามารถสร้างชนิดอื่น ๆ ของแองเคอ HTML LinkMixin
โดยการขยายการดำเนินงานเฉพาะของคุณด้วย
ในแง่ของ OCP "มิกซ์อิน" เป็นส่วนขยาย ในตัวอย่างข้างต้นYoutubeLink
คือเอนทิตีซอฟต์แวร์ของเราที่ถูกปิดเพื่อแก้ไข แต่เปิดสำหรับส่วนขยายผ่านการใช้มิกซ์อิน ลำดับชั้นวัตถุแบนออกซึ่งทำให้ไม่สามารถตรวจสอบประเภท อย่างไรก็ตามนี่ไม่ใช่สิ่งเลวร้ายจริง ๆ และฉันจะอธิบายเพิ่มเติมว่าการตรวจสอบประเภทโดยทั่วไปเป็นความคิดที่ไม่ดีและแบ่งความคิดด้วย polymorphism
โปรดทราบว่ามีความเป็นไปได้ที่จะทำการสืบทอดหลายวิธีด้วยวิธีนี้เนื่องจากextend
การใช้งานส่วนใหญ่สามารถผสมหลายวัตถุ:
_.extend(MyClass, Mixin1, Mixin2 /* [, ...] */);
สิ่งเดียวที่คุณต้องจำไว้คือการไม่ชนชื่อเช่น mixins เกิดขึ้นเพื่อกำหนดชื่อเดียวกันของคุณลักษณะหรือวิธีการบางอย่างที่พวกเขาจะถูกแทนที่ จากประสบการณ์ที่อ่อนน้อมถ่อมตนของฉันนี่เป็นปัญหาที่ไม่เกิดขึ้นและหากเกิดขึ้นนี่เป็นข้อบ่งชี้ของการออกแบบที่มีข้อบกพร่อง
หลักการทดแทนของ Liskov (LSP)
ลุงบ๊อบนิยามโดย:
คลาสที่ได้รับมาต้องถูกแทนที่สำหรับคลาสพื้นฐานของพวกเขา
หลักการนี้เก่าแล้วอันที่จริงคำจำกัดความของลุงบ๊อบไม่ได้แยกความแตกต่างของหลักการที่ทำให้ LSP ยังคงเกี่ยวข้องกับ OCP อย่างใกล้ชิดโดยข้อเท็จจริงที่ว่าในตัวอย่างกลยุทธ์ข้างต้นจะใช้ supertype ชนิดเดียวกัน ( IBehavior
) ดังนั้นให้ดูที่นิยามดั้งเดิมโดย Barbara Liskov และดูว่าเราสามารถหาข้อมูลอื่นเกี่ยวกับหลักการนี้ที่ดูเหมือนทฤษฎีบททางคณิตศาสตร์ได้หรือไม่:
อะไรคือสิ่งที่ต้องการที่นี่สิ่งที่ต้องการคุณสมบัติทดแทนต่อไปนี้คือ: ถ้าสำหรับวัตถุแต่ละo1
ชนิดS
มีวัตถุo2
ประเภทT
เช่นว่าสำหรับโปรแกรมทั้งหมดP
ที่กำหนดไว้ในแง่ของT
พฤติกรรมของP
ไม่เปลี่ยนแปลงเมื่อo1
มีการเปลี่ยนแทนo2
แล้วเป็นชนิดย่อยของS
T
ปล่อยให้ยักบนนี้สักพักสังเกตว่ามันไม่ได้พูดถึงคลาสเลย ใน JavaScript คุณสามารถติดตาม LSP ได้จริงแม้ว่าจะไม่ได้อ้างอิงตามระดับชั้นอย่างชัดเจน หากโปรแกรมของคุณมีรายการของวัตถุ JavaScript อย่างน้อยสองอย่างที่:
- จะต้องมีการคำนวณในลักษณะเดียวกัน
- มีพฤติกรรมเหมือนกันและ
- แตกต่างกันอย่างสิ้นเชิง
... จากนั้นวัตถุจะถูกพิจารณาว่ามี "ประเภท" เหมือนกันและมันไม่สำคัญสำหรับโปรแกรม นี้เป็นหลักความแตกต่าง ในความหมายทั่วไป คุณไม่จำเป็นต้องรู้ชนิดย่อยจริงถ้าคุณใช้อินเทอร์เฟซ OCP ไม่ได้พูดอะไรที่ชัดเจนเกี่ยวกับเรื่องนี้ นอกจากนี้ยังระบุข้อผิดพลาดในการออกแบบที่โปรแกรมเมอร์ส่วนใหญ่ทำ:
เมื่อใดก็ตามที่คุณรู้สึกอยากตรวจสอบชนิดย่อยของวัตถุคุณมักจะทำผิดพลาด
ตกลงดังนั้นมันอาจไม่ผิดตลอดเวลา แต่ถ้าคุณมีความต้องการที่จะทำการตรวจสอบบางประเภทด้วยinstanceof
หรือ enums คุณอาจกำลังทำโปรแกรมอีกเล็กน้อยที่ซับซ้อนกว่าสำหรับตัวคุณเอง แต่นี่ไม่ใช่กรณีเสมอไป hacks รวดเร็วและสกปรกที่จะได้รับสิ่งที่ทำงานเป็นสัมปทานโอเคที่จะทำในใจของฉันถ้าการแก้ปัญหาคือมีขนาดเล็กพอและถ้าคุณปฏิบัติrefactoring เลือดเย็นก็อาจได้รับการปรับปรุงเมื่อการเปลี่ยนแปลงต้องการมัน
มีวิธีแก้ไข "การออกแบบที่ผิดพลาด" ขึ้นอยู่กับปัญหาจริง:
- คลาส super ไม่ได้เรียกข้อกำหนดเบื้องต้นบังคับให้ผู้เรียกทำเช่นนั้นแทน
- คลาส super ไม่มีเมธอดทั่วไปที่ผู้เรียกต้องการ
ทั้งสองอย่างนี้เป็น "ความผิดพลาด" ในการออกแบบรหัสทั่วไป มีคู่ของ refactorings ที่แตกต่างกันที่คุณสามารถทำเช่นนี้เป็นวิธีการดึงขึ้นหรือ refactor เป็นรูปแบบดังกล่าวในรูปแบบของผู้เข้าชม
ฉันชอบรูปแบบผู้เข้าชมมากเพราะมันสามารถดูแลสปาเก็ตตี้แบบ if-statement ขนาดใหญ่ได้และมันง่ายกว่าที่จะใช้มากกว่าสิ่งที่คุณคิดในโค้ดที่มีอยู่ สมมติว่าเรามีบริบทดังต่อไปนี้:
public class Context {
public void doStuff(string query) {
// outcome no. 1
if (query.Equals("Hello")) {
System.out.println("Hello world!");
}
// outcome no. 2
else if (query.Equals("Bye")) {
System.out.println("Good bye cruel world!");
}
// a change request may require another outcome...
}
}
// usage:
Context c = new Context();
c.doStuff("Hello");
// prints "Hello world"
c.doStuff("Bye");
// prints "Bye"
ผลลัพธ์ของคำสั่ง if สามารถแปลเป็นผู้เยี่ยมชมของพวกเขาแต่ละคนขึ้นอยู่กับการตัดสินใจและรหัสที่จะเรียกใช้ เราสามารถแยกสิ่งเหล่านี้ออกได้:
public interface IVisitor {
public bool canDo(string query);
public void doStuff();
}
// outcome 1
public class HelloVisitor implements IVisitor {
public bool canDo(string query) {
return query.Equals("Hello");
}
public void doStuff() {
System.out.println("Hello World");
}
}
// outcome 2
public class ByeVisitor implements IVisitor {
public bool canDo(string query) {
return query.Equals("Bye");
}
public void doStuff() {
System.out.println("Good bye cruel world");
}
}
ณ จุดนี้หากโปรแกรมเมอร์ไม่ทราบเกี่ยวกับรูปแบบของผู้เข้าชมเขาควรนำคลาสบริบทมาใช้แทนเพื่อตรวจสอบว่าเป็นประเภทที่แน่นอนหรือไม่ เนื่องจากคลาสผู้เยี่ยมชมมีcanDo
เมธอดบูลีนผู้พัฒนาสามารถใช้การเรียกเมธอดนั้นเพื่อตรวจสอบว่าเป็นวัตถุที่เหมาะสมในการทำงานหรือไม่ คลาสบริบทสามารถใช้ผู้เข้าชมทั้งหมด (และเพิ่มใหม่) เช่นนี้:
public class Context {
private ArrayList<IVisitor> visitors = new ArrayList<IVisitor>();
public Context() {
visitors.add(new HelloVisitor());
visitors.add(new ByeVisitor());
}
// instead of if-statements, go through all visitors
// and use the canDo method to determine if the
// visitor object is the right one to "visit"
public void doStuff(string query) {
for(IVisitor visitor : visitors) {
if (visitor.canDo(query)) {
visitor.doStuff();
break;
// or return... it depends if you have logic
// after this foreach loop
}
}
}
// dynamically adds new visitors
public void addVisitor(IVisitor visitor) {
if (visitor != null)
visitors.add(visitor);
}
}
รูปแบบทั้งสองเป็นไปตาม OCP และ LSP อย่างไรก็ตามทั้งคู่ระบุสิ่งต่าง ๆ เกี่ยวกับพวกเขา ดังนั้นรหัสจะมีลักษณะอย่างไรถ้ามันละเมิดหนึ่งในหลักการ?
ละเมิดหลักการหนึ่ง แต่ปฏิบัติตามหลักการอื่น
มีวิธีที่จะทำลายหนึ่งในหลักการ แต่ก็ยังมีอื่น ๆ ตามมา ตัวอย่างด้านล่างดูเหมือนเป็นการประดิษฐ์ด้วยเหตุผลที่ดี แต่ฉันได้เห็นจริง ๆ แล้วเกิดขึ้นในรหัสการผลิต (และยิ่งแย่ลง):
เป็นไปตาม OCP แต่ไม่ใช่ LSP
ให้บอกว่าเรามีรหัสที่ได้รับ:
public interface IPerson {}
public class Boss implements IPerson {
public void doBossStuff() { ... }
}
public class Peon implements IPerson {
public void doPeonStuff() { ... }
}
public class Context {
public Collection<IPerson> getPersons() { ... }
}
รหัสชิ้นนี้เป็นไปตามหลักการเปิดปิด หากเรากำลังเรียกGetPersons
วิธีของบริบทเราจะทำให้ทุกคนได้รับประโยชน์จากการใช้งานของพวกเขาเอง ซึ่งหมายความว่า IPerson ถูกปิดเพื่อทำการปรับเปลี่ยน แต่เปิดเพื่อขยาย อย่างไรก็ตามสิ่งต่าง ๆ เปลี่ยนเป็นมืดเมื่อเราต้องใช้:
// in some routine that needs to do stuff with
// a collection of IPerson:
Collection<IPerson> persons = context.getPersons();
for (IPerson person : persons) {
// now we have to check the type... :-P
if (person instanceof Boss) {
((Boss) person).doBossStuff();
}
else if (person instanceof Peon) {
((Peon) person).doPeonStuff();
}
}
คุณต้องทำการตรวจสอบประเภทและการแปลงประเภท! โปรดจำไว้ว่าฉันได้กล่าวถึงข้างต้นวิธีการตรวจสอบประเภทเป็นสิ่งที่ไม่ดี ? ไม่นะ! แต่อย่ากลัวเลยดังที่ได้กล่าวมาแล้วข้างต้นอาจจะทำการปรับโครงสร้างแบบดึงขึ้นหรือใช้รูปแบบของผู้เข้าชม ในกรณีนี้เราสามารถทำการดึงกลับขึ้นมาใหม่หลังจากเพิ่มวิธีทั่วไป:
public class Boss implements IPerson {
// we're adding this general method
public void doStuff() {
// that does the call instead
this.doBossStuff();
}
public void doBossStuff() { ... }
}
public interface IPerson {
// pulled up method from Boss
public void doStuff();
}
// do the same for Peon
ประโยชน์ตอนนี้คือคุณไม่จำเป็นต้องรู้ประเภทที่แน่นอนอีกต่อไปติดตาม LSP:
// in some routine that needs to do stuff with
// a collection of IPerson:
Collection<IPerson> persons = context.getPersons();
for (IPerson person : persons) {
// yay, no type checking!
person.doStuff();
}
ติดตาม LSP แต่ไม่ใช่ OCP
ให้ดูโค้ดบางตัวที่ตามหลัง LSP แต่ไม่ใช่ OCP มันเป็นสิ่งที่วางแผนไว้ แต่ทนกับฉันในเรื่องนี้มันเป็นความผิดพลาดเล็กน้อย:
public class LiskovBase {
public void doStuff() {
System.out.println("My name is Liskov");
}
}
public class LiskovSub extends LiskovBase {
public void doStuff() {
System.out.println("I'm a sub Liskov!");
}
}
public class Context {
private LiskovBase base;
// the good stuff
public void doLiskovyStuff() {
base.doStuff();
}
public void setBase(LiskovBase base) { this.base = base }
}
รหัสทำ LSP เนื่องจากบริบทสามารถใช้ LiskovBase โดยไม่ทราบชนิดที่แท้จริง คุณคิดว่ารหัสนี้เป็นไปตาม OCP เช่นกัน แต่ดูอย่างใกล้ชิดว่าคลาสปิดจริงหรือไม่ เกิดอะไรขึ้นถ้าdoStuff
วิธีการทำมากกว่าแค่พิมพ์บรรทัด?
คำตอบถ้าเป็นไปตาม OCP นั้นเป็นเพียง: ไม่มันไม่ใช่เพราะในการออกแบบวัตถุนี้เราจำเป็นต้องแทนที่รหัสอย่างสมบูรณ์ด้วยสิ่งอื่น วิธีนี้จะเปิดเวิร์มที่ตัดและวางได้ในขณะที่คุณต้องคัดลอกโค้ดจากคลาสฐานเพื่อให้สิ่งต่าง ๆ ทำงานได้ doStuff
วิธีแน่ใจว่าจะเปิดให้บริการสำหรับการขยาย แต่มันก็ไม่ได้ปิดสนิทสำหรับการปรับเปลี่ยน
เราสามารถใช้รูปแบบวิธีการแม่แบบนี้ รูปแบบเมธอดเทมเพลตเป็นเรื่องธรรมดาในเฟรมเวิร์กที่คุณอาจใช้โดยไม่ทราบ (เช่นคอมโพเนนต์การแกว่งของ Java, รูปแบบ c # และส่วนประกอบ ฯลฯ ) นี่คือวิธีหนึ่งในการปิดdoStuff
วิธีการปรับเปลี่ยนและตรวจสอบให้แน่ใจว่าปิดอยู่โดยทำเครื่องหมายด้วยfinal
คำหลักของ java คำหลักนั้นป้องกันไม่ให้ผู้อื่นทำการคลาสย่อยเพิ่มเติม (ใน C # คุณสามารถใช้sealed
เพื่อทำสิ่งเดียวกัน)
public class LiskovBase {
// this is now a template method
// the code that was duplicated
public final void doStuff() {
System.out.println(getStuffString());
}
// extension point, the code that "varies"
// in LiskovBase and it's subclasses
// called by the template method above
// we expect it to be virtual and overridden
public string getStuffString() {
return "My name is Liskov";
}
}
public class LiskovSub extends LiskovBase {
// the extension overridden
// the actual code that varied
public string getStuffString() {
return "I'm sub Liskov!";
}
}
ตัวอย่างนี้ติดตาม OCP และดูเหมือนว่างี่เง่าซึ่งก็เป็นเช่นนั้น แต่ลองจินตนาการว่านี่จะทำให้โค้ดมีขนาดใหญ่ขึ้น ฉันเห็นการปรับใช้รหัสในการผลิตโดยที่ subclasses จะแทนที่ทุกอย่างอย่างสมบูรณ์และโค้ดที่ถูกแทนที่นั้นส่วนใหญ่จะถูกตัดวางระหว่างการใช้งาน มันใช้งานได้ แต่เช่นเดียวกับการทำสำเนารหัสทั้งหมดยังเป็นการตั้งค่าสำหรับฝันร้ายการบำรุงรักษา
ข้อสรุป
ฉันหวังว่าทั้งหมดนี้จะเป็นการเคลียร์คำถามบางข้อเกี่ยวกับ OCP และ LSP และความแตกต่าง / ความคล้ายคลึงกันระหว่างพวกเขา มันง่ายที่จะยกเลิกพวกเขาเหมือนกัน แต่ตัวอย่างข้างต้นควรแสดงว่าพวกเขาไม่ได้
โปรดทราบว่าการรวบรวมจากโค้ดตัวอย่างด้านบน:
OCP เป็นเรื่องเกี่ยวกับการล็อครหัสการทำงานลง แต่ยังคงเปิดไว้อย่างใดด้วยจุดส่วนขยายบางประเภท
นี่คือการหลีกเลี่ยงการทำสำเนารหัสโดยการห่อหุ้มรหัสที่เปลี่ยนแปลงเช่นเดียวกับตัวอย่างของรูปแบบวิธีการของแม่แบบ นอกจากนี้ยังช่วยให้การล้มเหลวอย่างรวดเร็วเนื่องจากการเปลี่ยนแปลงที่รุนแรงนั้นเจ็บปวด (เช่นเปลี่ยนสถานที่เดียวทำลายทุกที่อื่น) เพื่อประโยชน์ในการบำรุงรักษาแนวคิดของการห่อหุ้มการเปลี่ยนแปลงเป็นสิ่งที่ดีเพราะการเปลี่ยนแปลงเกิดขึ้นเสมอ
LSP เกี่ยวกับการให้ผู้ใช้จัดการกับวัตถุต่าง ๆ ที่นำ supertype มาใช้โดยไม่ตรวจสอบว่าเป็นประเภทใด นี่คือเนื้อแท้สิ่งที่แตกต่างเป็นเรื่องเกี่ยวกับ
หลักการนี้ให้ทางเลือกในการทำการตรวจสอบประเภทและการแปลงประเภทที่สามารถออกจากมือได้เมื่อจำนวนของประเภทเพิ่มขึ้นและสามารถทำได้ผ่านการปรับโครงสร้างแบบดึงขึ้นหรือใช้รูปแบบเช่นผู้เข้าชม