Перекрывание и переопределение методов при наследовании
В OverScript методы в производных классах перекрывают методы в базовом, но не переопределяют их, если вы не используете модификаторы virtual/override.
Child c=new Child(); Parent p=c; c.Test(); //Hello p.Test(); //Test class Parent{ public Test(){ WriteLine(GetStr()); } string GetStr(){ return "Test"; } } class Child:Parent{ string GetStr(){ return "Hello"; } }
Если нужно, чтобы p.Test() выводил "Hello", а не "Test", то сделаем GetStr() виртуальным:
Child c=new Child(); Parent p=c; c.Test(); //Hello p.Test(); //Hello class Parent{ public Test(){ WriteLine(GetStr()); } virtual string GetStr(){ return "Test"; } } class Child:Parent{ override string GetStr(){ return "Hello"; } }
Если же нужно, чтобы c.Test() выводил "Test", а не "Hello", то сделаем Test() фиксированным (fixed). В таком случае он всегда будет обращаться к GetStr() из Parent.
Child c=new Child(); Parent p=c; c.Test(); //Test p.Test(); //Test class Parent{ public fixed Test(){ WriteLine(GetStr()); } string GetStr(){ return "Test"; } } class Child:Parent{ string GetStr(){ return "Hello"; } }
Теперь посмотрим, как сделано в Python, PHP, Java, C#.
Python:
class Parent(object): def GetStr(self): return "Test" def Test(self): print(self.GetStr()) class Child(Parent): def GetStr(self): return "Hello" c=Child() c.Test() #Hello
Как видим, используется GetStr из Child. В Python все методы являются виртуальными.
PHP:
$child = new Child(); $child->Test(); //Hello class ParentClass { function GetStr(){ return 'Test'; } public function Test(){ echo($this->GetStr()); } } class Child extends ParentClass { function GetStr(){ return 'Hello'; } }
Тут также используется GetStr из Child.
Java:
public class MyClass{ public static void main(String args[]){ Child c=new Child(); Parent p=c; c.Test(); //Hello p.Test(); //Hello } } class Parent { public void Test(){ System.out.println(GetStr()); } String GetStr() { return("Test"); } } class Child extends Parent{ String GetStr() { return("Hello"); } }
В обоих вызовах используется GetStr из Child. Т.е. методы виртуальные, даже если не используется аннотация @Override
.
C#:
using System; class Program{ static void Main(string[] args) { Child c = new Child(); Parent p = c; c.Test(); //Test p.Test(); //Test } } class Parent{ public void Test(){ Console.WriteLine(GetStr()); } string GetStr() { return "Test"; } } class Child:Parent{ string GetStr(){ return "Hello"; } }
В C# метод GetStr в производном классе не повлиял на работу метода Test. В терминологии OverScript все методы в C# фиксированные. Расфиксировать их нельзя, а можно только сделать виртуальными.
В OverScript методы по умолчанию наследуются с перекомпилированием (перестроением). Т.е. в производный класс копируется код метода, который компилируется в новом окружении, а значит будут использоваться методы, которые перекрывают методы из базового класса. Чтобы копировался не код, а только ссылка на метод, нужно использовать модификатор fixed. Чтобы метод вообще не копировался в производный класс, нужно пометить его модификатором exclusive.
Когда метод наследуется с перекомпилированием, то его новая версия будет использовать в том числе перекрывающие статические функции, поля и константы.
Child c=new Child(); Parent p=c; c.Test(); //GetStr at Child: Hello; 2 p.Test(); //GetStr at Parent: Test; 1 class Parent{ static string Str="Test"; const int X=1; public Test(){ WriteLine(GetStr()); } static string GetStr(){ return "GetStr at Parent: "+Str+"; "+X; } } class Child:Parent{ static string Str="Hello"; const int X=2; static string GetStr(){ return "GetStr at Child: "+Str+"; "+X; } }
Под компилированием я имею в виду построение из кода дерева операций, которое в абстрактном виде представляет структуру программы.
Проще всего понять наследование в OverScript, мысленно копируя код функций в производные классы. Методы копируются один за другим, и если в классе получается несколько с одинаковыми сигнатурами (имя + типы параметров) то вызывается всегда последний (на самом деле, интерпретатор их видит в обратном порядке и вызывает первый). При этом вы можете вызывать не только последнюю, а любую версию метода добавляя к имени метода соответствующее порядку кол-во символов $.
Baz b=new Baz(); b.Test(); //At Baz! b.$Test(); //At Bar! b.$$Test(); //At Foo! class Foo{ public Test(){WriteLine("At Foo!");} } class Bar:Foo{ public Test(){WriteLine("At Bar!");} } class Baz:Bar{ public Test(){WriteLine("At Baz!");} }
Один $ - это вызвать предыдущий, $$ - предпредыдущий и т.д.
С конструкторами:
Baz b=new Baz(); //At Foo! class Foo{ New(){WriteLine("At Foo!");} } class Bar:Foo{ New(){$New();} } class Baz:Bar{ New(){$New();} }
В этом примере конструктор Baz вызывает конструктор Bar, который вызывает конструктор Foo.