Wątek przeniesiony 2014-09-30 11:06 z Off-Topic przez Shalom.

Wszystko jest interfejsem

0

Brakuje mi działu typu "Ogólne dyskusje o programowaniu" bo nie wiem gdzie o tym napisać - w "Newbie" temat zginie, w "Nietuzinkowe tematy" nikt nie zagląda, a "Inżynieria oprogramowania" się wręcz źle kojarzy.
Ale do rzeczy:

Często mi się zdarza że chciałbym wydzielić z kilku obiektów wspólną część do interfejsu, albo je sprowadzić do wspólnej klasy abstrakcyjnej.
Równie często nie mogę tego zrobić bo architektura aplikacji już jest narzucona przez kogoś "z góry" lub po prostu jest zabunkrowana w zewnętrznej dll-ce.
Prostym przykładem mogą być tu kontrolki DevExpressa gdzie wiele komponentów ma metody "BeginUpdate", "EndUpdate", ale nie są one z niczego dziedziczone - musiałbym więc każdą kontrolkę opakować w oddzielną klasę ze wspólnym interfejsem żeby móc z niego skorzystać. W innych przypadkach gdzie zwracana jest już klasa konkretnego typu wstrzyknięcie interfejsu może być nawet niemożliwe jeśli metody nie są wirtualne.
W praktyce zna się dużo wzorców ale nigdzie nie można z nich na niezaprojektowanym przez siebie projekcie skorzystać.

Zastanawiam się jakie by były minusy gdyby w językach silnie typowanych z podejścia "wszystko jest obiektem" przejść dalej na "wszystko jest interfejsem"

Na przykładzie języka C# klasa:

public class Class
{
  public void A(int a)
  {
    // sth
  }

  public int B(string a)
  {
    // sth
  }
}

mogłaby przecież automatycznie być interpretowana jako (słabo przemyślana składnia, ale chodzi o idee):

public class Class: IvoidAint, IintBstring
{
  public void A(int a)
  {
    // sth
  }

  public int B(string a)
  {
    // sth
  }
}

public interface IvoidAint
{
  void A(int);
}

public interface IintBstring
{
  int B(string);
}

dzięki czemu mógłbym użyć innej klasy, która zawiera identyczne metody, bez zmieniania tych klas
Oczywiście nazwy interfejsów i sposób "rzutowania" klas na te interfejsy byłyby do omówienia, ale ogólnie idea jest prosta

Można by było oczywiście tworzyć dalej własne interfejsy tak jak można to robić teraz

public interface IMojInterfejs
{
  void A(int);
  int B(string);
}

co byłoby po prostu tłumaczone na:

public interface IMojInterfejs : IvoidAint, IintBstring { }

wiem że akurat w C# są takie słówka jak "dynamic", ale to trochę odejście od silnego typowania i osobiście mi się nie podoba przez większą podatność na błędy.
Co sądzicie o tym pomyśle? Dlaczego nie ma czegoś takiego lub nic mi o tym nie wiadomo? A może w którymś języku już od dawna ta idea jest stosowana?

0

Idea ta jest stosowana w projektowaniu systemów. To o czym piszesz wynaleziono zaraz po kole. Oczywiście nie tworzy się osobnego interfejsu dla każdej właściwości czy metody, bo oznaczałoby to masę pisania i niewiele z tego korzyści. Wyciąga się raczej kluczowe funkcjonalności i dzieli pomiędzy interfejsy. Ja osobiście dodaje interfejs tylko wtedy, gdy się do niego odwołuję chociaż raz, z tym że ja nie piszę dllek na użytek publiczny. Przykład, który przytoczyłeś to nic innego jak ograniczone myślenie twórców DevExpress

0

Co do interfejs per metoda/property to imo zdeczka to bezsensu. Lepiej by bylo wprowadzic taki trick zwany structural subtyping,

Co to konkretnie daje?
Mysle, ze wlasnie adresuje twoj problem, pokaze na przykladzie:
Korzystamy z biblioteki juz gotowej, ktora ma 3 klasy: Foo, Bar i Baz. Kazda z tych klas korzysta z zasobow, ktore trzeba zwolnic, ale jakims dziwnym trafem, nie implementuje interfejsu IDisposable i nie mozemy uzyc konstrukcji using.
Dzieki strukturalnemu dziedziczeniu, mozemy stworzyc funkcje, przyjmujaca parametr generyczny wymuszajacy pewna strukture klasy (np. metode close()).

Jak wyglada 'w miare udana' implementacja:

class Foo {
  def close(): Unit
}

class Bar {
  def close(): Unit
}

class Baz {
  def close(): Unit
}

def using[A <: { def close(): Unit}](a: A, f: A => Unit) = { f(a); a.close() }

Niemniej jednak, nic takiego w C# nie ma, ale koncepcja sama w sobie jest fajne. :]

Edit: Jeszcze dodam, ze w scali mozna to napisac lepiej, tj.

  1. Definiujemy typeclasse Closable
trait Closable[A] {
  def close(a: A): Unit
}
  1. Definiujemy instancje typeclassy dla klas z zewnetrznej biblioteki.
// w obiekcie towarzyszacym, zeby nie trzeba bylo tego importowac
// do tego jako implicity (zaraz bedzie jasne dlaczego)
object Closable {
  implicit val barClosable: Closable[Bar] = new Closable[Bar] {
    def close(bar: Bar): Unit = bar.close()
  }

  // tworzymy funkcje pomocnicza
  def using[A](a: A)(f: A => Unit)
              (implicit ev: Closable[A]) // przekazujemy niejawnie dowod, na zachowanie parametru A
    = { f(a); ev.close(a) }
}

Gotowe.

Mozemy jeszcze zdefiniowac "skladnie", tj. klase ClosableOps, do ktorego bedziemy mieli niejawna konwersje, klas dla ktorych wystepuje instancja typeclassy Closable

object Closable {
  // implicit class to syntax sugar na definiowanie oddzielnej niejawnej konwersji.
  implicit class ClosableOps[A](a: A)(implicit ev: Closable[A]) {
    def close(): Unit = ev.close(a)
  }

  // i teraz nasz using wyglada tak:
  def using[A: Closable](a: A)(f: A => Unit) = { f(a); a.close() }
}

Jako ciekawostka - w mniej-wiecej ten sam sposob dziala cale scalaz. :P

0

To wiadome że można tak zrobić i o tym wspomniałem
Ale nie piszę tutaj o ręcznym klepaniu interfejsów tylko o właściwości języka której mi brakuje - chodzi mi o to że tworzeniem interfejsów mógłby się zająć kompilator

Wiadomo teoria teorią a w praktyce... w każdej pracy w jakiej byłem system był średnio zaprojektowany.
Idea jest taka żeby można było przyjemnie pracować nawet na średnio zaprojektowanym projekcie

0

@n0name_l: właśnie o coś takiego mi chodziło
o korzystanie ze wspólnych cech klas bez zmiany ich implementacji - ten "trick" to ładne rozwinięcie mojego pomysłu - bardzo szkoda że nie spotkałem się z nim w żadnym języku
Interfejs per metoda rzeczywiście samo w sobie nie ma sensu, ale chciałem pokazać jak łatwo bez zmiany samej specyfikacji języka, a więc pewnie małym nakładem pracy można by było w dowolnym prawie języku wprowadzić takie "structural subtyping". Bo z interfejsu per metoda już do tego rozwiązania o krok

0

bardzo szkoda że nie spotkałem się z nim w żadnym języku

W scali jest, w ocamlu. Pewnie jeszcze w 100 innych jezykow, ktorych nie znam. :P

1

System typów .NET niestety tej funkcjonalności nie wspiera.

Można natomiast w językach .NET-owych zaimplementować statyczną funkcjonalność tego typu na poziomie kompilacji. Robi to np. F# za pomocą member constraints w inline functions.

type A() =
    member x.Dispose() =
        printf "I'm not implementing anything!"

let inline using x f =
    try
        f x
    finally
        (^T : (member Dispose: unit -> unit) x)

let func x = printfn "%A" x

using (System.IO.File.OpenRead("blah")) func
using (A()) func

Jako, że nie można tego zaprezentować w MSIL (zrzut z dekompilatora):

public static a @using<T, a>(T x, FSharpFunc<T, a> f)
{
    a _a;
    try
    {
        _a = f.Invoke(x);
    }
    finally
    {
        throw new NotSupportedException();
    }
    return _a;
}

To kod zostaje rozwiązany tak jak szablony w C++:

public static void main@()
{
    FileStream fileStream = File.OpenRead("blah");
    FSharpFunc<FileStream, Unit> _clou004013 = new Program.clo@13();
    try
    {
        Unit unit = _clou004013.Invoke(fileStream);
    }
    finally
    {
        fileStream.Dispose();
    }
    Program.A a = new Program.A();
    FSharpFunc<Program.A, Unit> _clou004014u002d1 = new Program.clo@14-1();
    try
    {
        Unit unit1 = _clou004014u002d1.Invoke(a);
    }
    finally
    {
        a.Dispose();
    }
}
0

W TypeScript też jest podobna funkcjonalność (chociaż może zrealizowana w inny sposób niż piszesz).

Przykładowo funkcja może przyjmować dowolny obiekt posiadający label:

function printLabel(labelledObj: {label: string}) {
  console.log(labelledObj.label);
}

Albo analogicznie:

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

W pewnym sensie to to samo co piszesz:

Inny przykład:

interface Named {
    name: string;
}

var x: Named;
// y’s inferred type is { name: string; location: string; }
var y = { name: 'Alice', location: 'Seattle' };
x = y;

Mamy jednocześnie silne typowanie i duck typing.

PS. Coś podobnego było chyba też w scali. @Wibowit ?

0

@msm W Scala jest Structural Typing. Wygląda to tak:

scala> def print(objWithLabel: { def label: String }) = println(objWithLabel.label)
print: (objWithLabel: AnyRef{def label: String})Unit
scala> case class Cat(label: String)
scala> class Dog {def label: String = "yyy"}	
scala> print(Cat("xxx"))
xxx
scala> print(new Dog)
yyy
scala> print("")
<console>:9: error: type mismatch;
 found   : String("")
 required: AnyRef{def label: String}
              print("")
                    ^

Mechanizm ten wykorzystuje pod spodem refleksje.

0

Jeszcze taki mechanizm ma Go:

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
    a = &v // a *Vertex implements Abser

    fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1