Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Паттерны в Java и C# 
:(
    Опции темы
batigoal
Дата 8.12.2005, 23:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Нелетучий Мыш
****


Профиль
Группа: Участник Клуба
Сообщений: 6423
Регистрация: 28.12.2004
Где: Санктъ-Петербургъ

Репутация: 24
Всего: 151



 Simple Factory
Служит для создания объектов различных классов на основании информации, переданной во время выполнения программы. Обычно эти классы наследуют от базового класса или интерфейса, т.е. обладают похожей функциональностью.
 Пример
В данном случае Фабрика производит объект Filter1 или Filter2 в зависимости от значения переданного в метод getFilter.

Java
Код

class SimpleFactory
{
    
    public static final String FILTER_1 = "filter1";
    public static final String FILTER_2 = "filter2";
    
    public Filter getFilter(String name)
    {
        if (name.equals(FILTER_1))
        {
            return new Filter1();
        }
        if (name.equals(FILTER_2))
        {
            return new Filter2();
        }
        return null;
    }
}

interface Filter
{
    //String filter(String text); 
}

class Filter1 implements Filter
{}

class Filter2 implements Filter
{}


C#
Код

class SimpleFactory
{
    public enum Filters
    {
        Filter1,
        Filter2
    }
   
    public IFilter GetFilter(Filters name)
    {
        IFilter f;
        switch (name)
        {
            case Filters.Filter1 :
                f = new Filter1();
                break;
            case Filters.Filter2 :
                f = new Filter2();
                break;
            default: 
                f = null;
                break;
        }
        return f;
    }
}

interface IFilter
{
    //String Filter(String text); 
}

class Filter1 : IFilter
{}

class Filter2 : IFilter
{}



Паттерн очень простой, потому и не включен в классические 23 паттерна.
 Преимущества
Позволяет избавиться от хардкодинга. Например, без этого паттерна нам пришлось бы писать 
Код

Filter f = new Filter1();
//....
Filter f2 = new Filter2();
//...
Filter f3;
if (a.equals("filter1"))) f3 = new Filter1();
else //....

 
Теперь представим что мы решили дать классам Filter<> более осмысленные имена. Нам пришлось бы прочесывать весь код и менять не один раз. С данным паттерном все просто, достаточно изменить класс SimpleFactory.

Паттерн добавляет слой абстракции, отделяя процесс создания объекта от его использования. Фабрика может применять более сложную логику создания объектов; например может создавать их через рефлекшн. 
 &nbsp;Factory
Используется, когда неизвестно, объект какого класса нужно будет создать. В отличие от SimpleFactory в Factory отсутствует явный код выбора класса (как в методе getFilter() в предыдущем случае). 
Этот выбор может осуществляться через полиморфизм.
&nbsp;Пример
Есть несколько классов Filter<>, наследующих от абстрактного Filter. Каждый из фильтров использует свои ImageFilter (и, вероятно еще к-л компоненты).

Java
Код

abstract class Filter
{
    protected ImageFilter imageFilter;
    public ImageFilter getImageFilter()
    {
        return imageFilter;
    } 
}

class Filter1 extends Filter
{
    public Filter1()
    {
        imageFilter = new ImageFilter1(); 
    }
}

class Filter2 extends Filter
{
    public Filter2()
    {
        imageFilter = new ImageFilter2(); 
    }
}


// ... 
Filter f = new Filter1();
ImageFilter imf = f.getImageFilter();


C#
Код

abstract class Filter
{
    protected ImageFilter imageFilter;
    public ImageFilter MyImageFilter
    {
        get
        {
            return imageFilter;
        }
    } 
}

class Filter1 : Filter
{
    public Filter1()
    {
        imageFilter = new ImageFilter1(); 
    }
}

class Filter2 : Filter
{
    public Filter2()
    {
        imageFilter = new ImageFilter2(); 
    }
}
//...
Filter f = new Filter1();
ImageFilter imf = f.MyImageFilter;

Суть паттерна в том, что обязанность выбора класса ImageFilter ложится на плечи сабклассов. Тем не 
менее точка доступа к ImageFilter одна - метод getImageFilter() базового класса.
&nbsp;Преимущества.
Фактически, те же, что и у SimpleFactory. Логика получения объектов (ImageFilter) отделена от основного кода.
 
 &nbsp;Abstract Factory
Несмотря на название, использовать абстрактный класс в этом паттерне необязательно. Самое главное отличие его от Factory в том, что один класс предоставляет интерфейс для доступа к группе объектов, а не одному объекту.
&nbsp;Пример
Мы пишем игру. Есть класс Level, от которого наследуют  Level1, Level2 и т п. а каждом уровне - свои монстры двух типов - "слабый" и "сильный". Будем считать что все классы <..>Monster наследуют от некого класса Monster. 

Java
Код

    class Level
    {
  protected Monster weakMonster, strongMonster;
  public Monster getWeakMonster()
  {
    return weakMonster;
  }
  public Monster getStrongMonster()
  {
    return strongMonster;
  }
    }

    class Level1 extends Level
    {
  public Level1()
  {
    weakMonster = new AlienMonster();
    strongMonster = new WaterMonster();
  }
    }

    class Level2 extends Level
    {
  public Level2()
  {
    weakMonster = new BadMonster();
    strongMonster = new BigMonster();
  }
    }


C#
Код

    class Level
    {
  protected Monster weakMonster, strongMonster;
  public Monster WeakMonster
  {
    get
    {
    return weakMonster;
    }
  }
  public Monster StrongMonster
  {
    get
    {
    return strongMonster;
    }
  }
    }

    class Level1 : Level
    {
  public Level1()
  {
    weakMonster = new AlienMonster();
    strongMonster = new WaterMonster();
  }
    }

    class Level2 : Level
    {
  public Level2()
  {
    weakMonster = new BadMonster();
    strongMonster = new BigMonster();
  }
    }

Здесь класс Level и является Абстрактной Фабрикой, т.к. дает доступ к группе объектов (weakMonster, strongMonster). Теперь можно писать такой код:
Код

Level l = new Level1();
Monster m1 = l.getStrongMonster();
//....
l = new Level2();
Monster m2 = l.getWeakMonster();

Таким образом, основной код понятия не имеет, каких монстров он использует.

&nbsp;Преимущества. 
Использование Abstract Factory, как и Factory, позволяет писать код, который "не знает" какие конкретно объекты он использует. Наследование позволяет сабклассам самим создавать об3екты, которые им нужны; полиморфизм дает возможность использовать их. Это упрощает как написание, так и поддержку кода, делает его более робастным. 
 &nbsp;Singleton
"Одиночка". Паттерн проименяется в случае, когда важно, чтобы в данный момент времени существовал один единственный объект какого-либо класса.
Зачем это нужно? 
Предположим мы написали простую базу данных. Наше АПИ включает в себя класс LockManager. Объект этого класса занимается тем, что предотвращает одновременный доступ клиентов к одному и тому же ряду в БД, то есть пока один клиент меняет запись (вызывает метод, передающий изменения), менеджер не позволяет никому другому эту запись изменить. 
Некий другой программер решил воспользоваться этим АПИ, и создал 2 объекта  LockManager. Теперь возможна ситуация, когда 2 клиента попытаются одновременно менять одну запись. Что может привести к нехорошим последствиям.
Естественно, нужно сделать так, чтобы можно было создать только 1 объект класса LockManager, а при попытке создать большее количество объектов, дать знать программеру об ошибке.
В целом подход один: делаем приватный конструктор, храним инстанс синглтона в самом классе как статик поле; получаем его статик методом типа getInstance(). 
Есть несколько способов реализации Синглтона. 
1. В  getInstance() проверяем, если ссылка не нул, возвращаем нул.
2. Если ссылка не нул, кидаем иксепшн.

&nbsp;Пример реализации 1.

Java
Код

class Singleton
{
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance()
    {
        if (instance == null) 
               instance = new Singleton();
        else return null;
        return instance;
    }

    public static void dispose()
    {
        instance = null;
    }
}


Код на шарпе не привожу, т.к. он на 99% такой же.

Вариант с иксепшном аналогичен; в строчке else return null; вместо ретурна нужно бросать иксепшн. Оба метода имеют свои преимущества и недостатки, но они настолько тонкие, что нет смысла о них здесь говорить.

&nbsp;Преимущества.
Дает полный контроль над количеством созданных объектов класса. Паттерн легко применить дкля случая когда нужно чтобы можно было создать не более N объектов; или точно  N объектов.
 
 &nbsp;Builder
Как следует из названия, паттерн собирает ряд объектов в одно целое, "строит" нечто новое. 
Сравним паттерн с Factory:
Factory: вы делаете заказ на определенную машину, фабрика ее производит. 
Builder: вы даете строителю нужные материалы. Тот что-то из них делает. 
Этот паттерн часто используется для построения ГУИ. Например, мы читаем данные из БД, причем для удобства сортировки выносим один столбец со всеми возможными должностями работников отдельно, в виде JList или JComboBox (ListBox, ComboBox). Лист удобен тогда, когда элементов в списке мало, комбобокс - когда их много. Можно легко решить проблему следующим псевдокодом:
Код

класс Строитель
       конструктор (Коллекция)
              если размер Коллекции > х заполнить КомбоБокс
              иначе заполнить Лист
 
Другим решением было бы создать два класса - строителя, каждый был бы ответственен за построение определенного ГУИ. В приведенном выше случае создание двух классов було бы слишком; но если бы мы решили создать 2 ОЧЕНЬ разных ГУИ, то 2 Билдера было бы решением более "чистым".  
Совсем необязательно чтобы Строитель строил визуальные компоненты.

&nbsp;Пример
Это очень надуманный пример, поясняющий тем не менее суть паттерна. В роли Builderов выступают ArrayListCollection и ArrayCollection. Каждый создает свой тип коллекции. Для выбора Билдера используется паттерн SimpleFactory (метод GetMyCollection) 
C#
Код

  [STAThread]
  static void Main(string[] args)
  {
    Console.WriteLine("1 or 2?");
    int choice = Console.Read() - 49;
    Factory f = new Factory();
    MyCollection mc = f.GetMyCollection(choice);
    mc.Print();
  }    
// ....

class Factory
{
  public MyCollection GetMyCollection(int i)
  {
    if (i == 0) return new ArrayCollection(new int[] {1, 3, 5, 7, 8, 9, 3});
    else return new ArrayListCollection(new decimal[] {3M, 6M, 33M, 320M});
  }
}

abstract class MyCollection
{
  public virtual void Print()
  {
    Console.WriteLine("MyCollection: ");
  }
}

class ArrayCollection : MyCollection
{
  public ICollection ic;
  public ArrayCollection(ICollection ic)
  {
    this.ic = ic;
  }
  public override void Print()
  {
    base.Print();
    foreach(object i in ic) Console.Write(" " + i);
  }
}

class ArrayListCollection : MyCollection
{
  private ArrayList aList;
  public ArrayListCollection(ICollection ic)
  {
    aList = new ArrayList();
    aList.AddRange( ic );
  }

  public override void Print()
  {
    base.Print();
    foreach(decimal m in aList) Console.Write(" {0:c}", m);
  }
}


&nbsp;Преимущества.
Билдер отделяет логику создания сложных компонентов от кода, использующего эти компоненты. Предположим мы на основе паттерна SimpleFactory выбираем Builder, который строит ГУИ для некого визуального компонента. Такой код легче поддерживать (ГУИ для каждого случая создается в своем классе/методе); легче реюзать; легче дополнять (например, третьим вариантом ГУИ)
 
 &nbsp;Adapter
Adapter преобразует ("адаптирует") один интерфейс в другой интерфейс. 
Может существовать множество причин для такого преобразования, например:
1. Класс А имеет сложный интерфейс, скорее "низкоуровневый". Для многих операций приходится делать несколько вызовов методов этого класса. Имеет смысл переработать этот интерфейс в более простой.
2. Интерфейс класса А  очень широк; но для определенных операций нам нужен более узкий, или специализированный интерфейс. 
Например, один и тот же класс может использоваься клиентами (удаленно), но может также быть использован администратором сервера. В таком объект не должен "показывать" клиентам всю функциональность.

Есть 2 основных варианта реализации Адаптера: через наследование и через композицию.

&nbsp;Примеры
Java 
Через наследование. Мы написали класс BinaryWriter, который работает с бинарными файлами, при этом шифруя информацию нашим собственным алгоритмом.
Но нам часто приходится писать текстовые данные. Применение Адаптера упрощает работу. 

Код

class BinaryWriter
{
    public seek(int offset) {...}
    public write(byte[] b, int offset) {...}
    public write(byte[] b) {...}
    .....  
}
class BinaryStringWriter extends BinaryWriter
{
    public void append(String s) {...}    
}

Теперь мы можем вместо вызовов нескольких методов класса  BinaryWriter использовать append. 
Но класс BinaryStringWriter по-прежнему обладает всеми паблик методами класса BinaryWriter! Это пример так называемого Two-Way Adapter - двустороннего адаптера, который может выступать как BinaryStringWriter, так и BinaryWriter.

Через композицию.
Класс Account  предоставляет интерфейс для управления аккаунтом клиента - снятием денег, удалением аккаунта и пр. Но показывать клиенту определенную функциональност, например метод deleteAccount, не слишком удачный вариант. 
Поэтому мы конструируем адаптер - класс ClientAccount, который содержит Account как приватное поле. Класс  ClientAccount предоставляет клиенту лишь часть интерфейса этого поля.
 
Код


class Account 
{
    public void withdrawMoney(double sum) {...}
    
    public void deleteAccount() {...}
}

class ClientAccount
{
    private Account account;
    public ClientAccount(Account a)
    {
       this.account= a;    
    }
    
    public void withdrawMoney(double sum)
    {
       account.withdrawMoney(sum);
    }    
}

&nbsp;Преимущества
Адаптер - это что-то вроде "переходника" - он позволяет рассматривать один интерфейс по-разному. Например, можно изменить интерфейс класса JTree, сделав его похожим на интерфейс класса ArrayList или HashMap.  Можно переделать интерфейс нескольких совершенно различных классов так, что он будет одним и тем же; такие адаптеры называются Plaggable Adapters. Если подумать, это очень интересная концепция. 
 &nbsp;Bridge

Адаптер преобразует интерфейс объекта, оставляя объект "нетронутым". Например, часы в наше время можно засунуть куда угодно, скажем в авторучку или чайник. Но часы останутся по сути своей часами; поменяется их вид и количество кнопочек, и только.
Bridge (мост) делает прямо противоположное - он "фиксирует" интерфейс объекта. Хороший пример Моста - электрическая розетка. Все что нас волнует - это как в нее засунуть вилку и какое там напряжение; сама же электроэнергия может идти с ТЭЦ, ГЭС, АЭС, солнечноей батареи или ветряка - нам все равно. И если завтра вместо ветряка будут пользовать солнечную батарею или белку в колесе, мы этого не заметим.
Часто Мост реализован как связка интерфейс-конкретный класс.

&nbsp;Пример
C#
Объект класса Calculator производит некие вычисления в методе Calculate. По завершении метода он вызывает соответствыющие методы всех зарегистрированных слушателей. 
Слушатели в свою очередь наследуют от интерфейса IBridge и таким образом гарантированно обладают методом DoCalculationFinished. 
Перед вычислением клиентское приложение регистрирует класс слушателя и его метод DoCalculationFinished будет вызван при появлении события CalculationFinished.
Таким образом, работа клиента основана на наличии класса с интерфейсом IBridge, больше его ничто не интересует.
Как показывает пример, сам Мост может делать далее с данными что угодно - слать по сети или сохранять на диск.
Код

class Calculator
{
        private double [] data; 
        public delegate Processor (double [] data);
        public event  Processor CalculationFinished;

         // .....
 
         public void Calculate()
         {
                 // ....
                 CalculationFinished(data);
         }
}

//...
Calculator c = new Calculator();
IBridge nb = getRegisteredBridge();
c.CalculationFinished += new Processor(nb.DoCalculationFinished);

class NetBridge : IBridge
{
        public void DoCalculationFinished(double[] data)
         {
                 // посылаем данные по сети 
         }
}

class LocalBridge : IBridge
{
        public void DoCalculationFinished(double[] data)
         {
                 // копируем данные в массив-лист, сохраняем как ХМЛ
         }
}

interface IBridge
{
        public void DoCalculationFinished(double[] data);
}


&nbsp;Преимущества
Паттерн сохраняет интерфейс клиента, сохраняя тем самым труд и время программера. 
Легко добавить нужную функциональность - например, можно добавить класс PrintBridge, который будет печатать результат, TransmitBridge, который отфильтрует данные и передаст их для дальнейших вычислений, и т п. 
 &nbsp;Composite
Обычно мы работаем либо с индивидуальными объектами, либо с коллекциями. Паттерн Composite 
объединяет оба случая. 
Пример композита - дерево. Дерево имеет листья (листья не могут иметь детей) и ноды, который  могут содержать другие ноды или листья. 
Суть паттерна в том, чтобы и ноды, и листья имели один интерфейс.

&nbsp;Пример
Java 
Предположим, что приложение описывает некую иерархическую структуру, например, сотрудников фирмы. Любой сотрудник будет описываться классом, наследующим от AbstractEmployee, т.е. интерфейс у всех классов будет один.
AbstractEmployee по сути есть часть древовидной структуры, и хранит ссылку на родителя и детей. 
Сабклассы AbstractEmployee по-своему имплементруют соответствующие методы; так например класс Employee (обычный сотрудник) на дереве будет листом, т.е. getSubordinates должен возвращать null. 

Код

аbstract class AbstractEmployee
{
     public abstract void addEmployee(AbstractEmployee ae);    // добавить подчиненного
     public abstract void deleteEmployee(AbstractEmployee ae);
     public abstract double getSalaryEmployee(); 
     public abstract double setSalary(double s); // установить зарплату
     public abstract boolean isLeaf(); // труе если это сотрудник без подчиненных
     public abstract Iterator getSubordinates(); // получить итератор коллекции подчиненных
     public AbstractEmployee getParent(); // получить ссылку на начальника
     public abstract String getName();
}

class Employee extends AbstractEmployee
{
 // имплементация интерфейса
  // ....
   public Iterator getSubordinates()
   {
         return null;
   }
   public boolean isLeaf()
   {
         return true;
   }
 //....
}

class Manager extends AbstractEmployee
{
    ArrayList children;
 // имплементация интерфейса
  // ....
   public Iterator getSubordinates()
   {
         return children.iterator();
   }
   public boolean isLeaf()
   {
         return false;
   }
 //....
}

&nbsp;Преимущества
Этот паттерн широко используется в Java и C#. Например, любая JPanel (Panel) содержит ссылки на свои компоненты (которые в свою очередь сами держат ссылки на их компоненты). Поскольку компоненты наследуют от Component (Control) то и интерфейс у них один. 
Композит позволяет упростить работу с похожими объектами. 
 &nbsp;Decorator
Декоратор похож на Адаптер в том смысле, что он преобразует интерфейс исходного класса. Однако, как и в случае с многими паттернами, цель Декоратора другая. 
Его задача - изменить интерфейс нескольких классов. Адаптер удобно использовать, делая его сабклассом нужного класса. А если нужно 20 классов "привести" к одному интерфейсу? Нужно создавать 20 сабклассов. 
Другой момент - Декоратор часто используется в ГУИ. 
Суть паттерна в том, что создается класс, содержащий поле; класс этого поля является суперклассом (или интерфейсом)  для тех классов, которые нам нужно "продекорировать".

&nbsp;Пример
Java 
Данный пример декорирует JPanel - когда указатель мыши находится над панелью, она обводится красной рамкой. В Декоратор мы помещаем не JPanel а JComponent; поэтому его можно использовать для декорации не только панели.

Код

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;

public class test extends JFrame
{
    test()
    {
        setSize(300, 300);
        JPanel p = new JPanel();
        JPanel p1 = new JPanel();
        p1.add(new JLabel(" * "));
        p.add(new SuperDecorator(p1));
        getContentPane().add(p, BorderLayout.CENTER);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
    public static void main(String[] args)
    {
        new test();
    }
}

class Decorator extends JComponent
{
    public Decorator(JComponent c)
    {
        setLayout(new BorderLayout());
        add("Center", c);
    }
}

class SuperDecorator extends Decorator
{
    private boolean isMouseOver;
    private JComponent c;
    
    public SuperDecorator(JComponent com)
    {
        super(com);
        c = com;
        MyAdapter ma = new MyAdapter();
        c.addMouseListener(ma);
    }
    
    class MyAdapter extends MouseAdapter
    {
        public void mouseEntered(MouseEvent me)
        {
            isMouseOver = true;
            repaint();
        }
        public void mouseExited(MouseEvent me)
        {
            isMouseOver = false;
            repaint();
        }
    }
    
    public void paint(Graphics g)
    {
        super.paint(g);
        if (isMouseOver)
        {
            Dimension size = super.getSize();
            g.setColor(Color.RED);
            g.drawRect(0, 0, size.width-1, size.height-1);
        }
    }
}

&nbsp;Преимущества
Один Декоратор может менять интерфейс для любого количества компонентов.  
 &nbsp;Facade
Что (отчасти) неудобно в ООП и при использовании паттернов - большое количество классов. 
Чем больше классов - тем сложнее работать. Фасад - это класс, который предоставляет простой интерфейс для работы, и прячет таким образом сложную бизнес логику приложения. 
Естественно, упрощение интерфейса приводит к потере гибкости при работе с Фасадом. Тем не менее, программист по-прежнему имеет доступ к "низкоуровневому" АПИ. Фасад - всего лишь класс, который упрощает жизнь в некоторых (или многих) случаях.
Можно использовать несколько Фасадов, наследующх от одного класса.

 &nbsp;Пример
Чтоб не писать чего-то сложного и нудного, все будет предельно схематично.
Есть классы A, B, C. Каждый раз нам приходится визывать ряд методов этих классов:
Код

class A
{
      public void MethodA() { ... }
}
class B
{
      public C MethodB1() { ... }
      public void MethodB2() { ... }
}
class C
{
      public void MethodC1() { ... }
      public void MethodC2() { ... }
}

//...
A a = new A();
B b = new B();
//...

a.MethodA();  // Блок 1
C c = b.MethodB1();
c.MethodC2();
// работаем с c
//.....
c.MethodC1(); // Блок 2
b.MethodB2();
c.Dispose();


Создадим класс Facade, в который вынесем Блок 1 и Блок 2 :

Код

public class Facade
{
      public Facade()
      {
             A a = new A();
             B b = new B();
      }
      public C DoFirstBlock()
      { Блок 1 } 

      public void DoSecondBlock(C c)
      { Блок 2}
}

Теперь все упрощается 
Код

Facade facade = new Facade();
C c = facade.DoFirstBlock();
//... 
facade.DoSecondBlock(c);

&nbsp;Преимущества
Упрощает интерфейс. Клиенту теперь не нужно знать тонкостей работы с  A, B и C. ВСе взаимодействие с кэтими классами ограничено двумя методами. С другой стороны, если придется поменять логику взаимодействия этих классов, вероятно, придется менять Фасад, или снова пользоваться исходными методами.  
 &nbsp;Flyweight
Объектно-ориентированный подход подразумевает создание классов, и естественно, объектов этих классов. Иногда нужно создать множество объектов одного класса. Если объекты отличаются только несколькими параметрами, применяется паттерн Flyweight.
Рассмотрим классический пример - папки на диске. У каждой папки свое название и иконка. Естественно, создавать свой объект для каждой папки - значит зодать множество почти одинаковых объектов.&


--------------------
"Чтобы правильно задать вопрос, нужно знать большую часть ответа" (Р. Шекли)
ЖоржЖЖ
PM WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0708 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.